char *zUncompress = 0; /* uncompress=? parameter (or NULL) */
char *zContent = 0; /* content=? parameter (or NULL) */
char *zLanguageid = 0; /* languageid=? parameter (or NULL) */
+ char *zLanguageidBits = 0; /* languageid_bits=? parameter (or NULL) */
assert( strlen(argv[0])==4 );
assert( (sqlite3_strnicmp(argv[0], "fts4", 4)==0 && isFts4)
const char *zOpt;
int nOpt;
} aFts4Opt[] = {
- { "matchinfo", 9 }, /* 0 -> MATCHINFO */
- { "prefix", 6 }, /* 1 -> PREFIX */
- { "compress", 8 }, /* 2 -> COMPRESS */
- { "uncompress", 10 }, /* 3 -> UNCOMPRESS */
- { "order", 5 }, /* 4 -> ORDER */
- { "content", 7 }, /* 5 -> CONTENT */
- { "languageid", 10 } /* 6 -> LANGUAGEID */
+ { "matchinfo", 9 }, /* 0 -> MATCHINFO */
+ { "prefix", 6 }, /* 1 -> PREFIX */
+ { "compress", 8 }, /* 2 -> COMPRESS */
+ { "uncompress", 10 }, /* 3 -> UNCOMPRESS */
+ { "order", 5 }, /* 4 -> ORDER */
+ { "content", 7 }, /* 5 -> CONTENT */
+ { "languageid", 10 }, /* 6 -> LANGUAGEID */
+ { "languageid_bits", 15 } /* 7 -> LANGUAGEID_BITS */
};
int iOpt;
zLanguageid = zVal;
zVal = 0;
break;
+
+ case 7: /* LANGUAGEID_BITS */
+ assert( iOpt==7 );
+ sqlite3_free(zLanguageidBits);
+ zLanguageidBits = zVal;
+ zVal = 0;
+ break;
}
}
sqlite3_free(zVal);
p->zLanguageid = zLanguageid;
zContent = 0;
zLanguageid = 0;
+ if( zLanguageidBits && p->zLanguageid && p->zContentTbl==0 ){
+ p->nLanguageidBits = atoi(zLanguageidBits);
+ if( p->nLanguageidBits>32 || p->nLanguageidBits<0 ){
+ rc = SQLITE_ERROR;
+ *pzErr = sqlite3_mprintf("languageid_bits parameter out of range");
+ goto fts3_init_out;
+ }
+ }
+
TESTONLY( p->inTransaction = -1 );
TESTONLY( p->mxSavepoint = -1 );
sqlite3_free(zUncompress);
sqlite3_free(zContent);
sqlite3_free(zLanguageid);
+ sqlite3_free(zLanguageidBits);
sqlite3_free((void *)aCol);
if( rc!=SQLITE_OK ){
if( p ){
assert( iCol>=0 && iCol<=p->nColumn+2 );
if( iCol==p->nColumn+1 ){
- /* This call is a request for the "docid" column. Since "docid" is an
- ** alias for "rowid", use the xRowid() method to obtain the value.
- */
- sqlite3_result_int64(pCtx, pCsr->iPrevId);
+ /* This call is a request for the "docid" column. The value currently
+ ** stored in pCsr->iPrevId is a rowid. Transform this to a docid and
+ ** return it. */
+ sqlite3_result_int64(pCtx, sqlite3Fts3RowidToDocid(p, pCsr->iPrevId));
}else if( iCol==p->nColumn ){
/* The extra column whose name is the same as the table.
** Return a blob which is a pointer to the cursor. */
*pRC = rc;
}
+static void fts3SqlExecI64(
+ int *pRC, /* Result code */
+ Fts3Table *p, /* The FTS3 table */
+ int eStmt, /* Index of statement to evaluate */
+ i64 iVal
+){
+ sqlite3_stmt *pStmt;
+ int rc;
+ if( *pRC ) return;
+ rc = fts3SqlStmt(p, eStmt, &pStmt, 0);
+ if( rc==SQLITE_OK ){
+ sqlite3_bind_int64(pStmt, 1, iVal);
+ sqlite3_step(pStmt);
+ rc = sqlite3_reset(pStmt);
+ }
+ *pRC = rc;
+}
+
/*
** This function ensures that the caller has obtained an exclusive
static int fts3InsertData(
Fts3Table *p, /* Full-text table */
sqlite3_value **apVal, /* Array of values to insert */
- sqlite3_int64 *piDocid /* OUT: Docid for row just inserted */
+ sqlite3_int64 *piRowid, /* OUT: Rowid for row just inserted */
+ i64 iRowid /* Explicit rowid, if piRowid==NULL */
){
int rc; /* Return code */
sqlite3_stmt *pContentInsert; /* INSERT INTO %_content VALUES(...) */
if( p->zContentTbl ){
- sqlite3_value *pRowid = apVal[p->nColumn+3];
+ sqlite3_value *pRowid;
+ assert( p->nLanguageidBits==0 && piRowid );
+ pRowid = apVal[p->nColumn+3];
if( sqlite3_value_type(pRowid)==SQLITE_NULL ){
pRowid = apVal[1];
}
if( sqlite3_value_type(pRowid)!=SQLITE_INTEGER ){
return SQLITE_CONSTRAINT;
}
- *piDocid = sqlite3_value_int64(pRowid);
+ *piRowid = sqlite3_value_int64(pRowid);
return SQLITE_OK;
}
** defined columns in the FTS3 table, plus one for the docid field.
*/
rc = fts3SqlStmt(p, SQL_CONTENT_INSERT, &pContentInsert, &apVal[1]);
- if( rc==SQLITE_OK && p->zLanguageid ){
- rc = sqlite3_bind_int(
- pContentInsert, p->nColumn+2,
- sqlite3_value_int(apVal[p->nColumn+4])
- );
+ if( rc==SQLITE_OK ){
+ if( piRowid==0 ){
+ sqlite3_bind_int64(pContentInsert, 1, iRowid);
+ }
+ if( p->zLanguageid ){
+ rc = sqlite3_bind_int(
+ pContentInsert, p->nColumn+2,
+ sqlite3_value_int(apVal[p->nColumn+4])
+ );
+ }
}
if( rc!=SQLITE_OK ) return rc;
** In FTS3, this is an error. It is an error to specify non-NULL values
** for both docid and some other rowid alias.
*/
- if( SQLITE_NULL!=sqlite3_value_type(apVal[3+p->nColumn]) ){
+ assert( p->nLanguageidBits==0 || piRowid==0
+ || sqlite3_value_type(apVal[1])!=SQLITE_NULL
+ );
+ if( piRowid && p->nLanguageidBits==0
+ && SQLITE_NULL!=sqlite3_value_type(apVal[3+p->nColumn])
+ ){
if( SQLITE_NULL==sqlite3_value_type(apVal[0])
&& SQLITE_NULL!=sqlite3_value_type(apVal[1])
){
if( rc!=SQLITE_OK ) return rc;
}
- /* Execute the statement to insert the record. Set *piDocid to the
+ /* Execute the statement to insert the record. Set *pRowid to the
** new docid value.
*/
sqlite3_step(pContentInsert);
rc = sqlite3_reset(pContentInsert);
- *piDocid = sqlite3_last_insert_rowid(p->db);
+ if( piRowid ){
+ *piRowid = sqlite3_last_insert_rowid(p->db);
+ }
return rc;
}
static void fts3DeleteTerms(
int *pRC, /* Result code */
Fts3Table *p, /* The FTS table to delete from */
- sqlite3_value *pRowid, /* The docid to be deleted */
+ i64 iRowid, /* The rowid to be deleted */
u32 *aSz, /* Sizes of deleted document written here */
int *pbFound /* OUT: Set to true if row really does exist */
){
assert( *pbFound==0 );
if( *pRC ) return;
- rc = fts3SqlStmt(p, SQL_SELECT_CONTENT_BY_ROWID, &pSelect, &pRowid);
+ rc = fts3SqlStmt(p, SQL_SELECT_CONTENT_BY_ROWID, &pSelect, 0);
if( rc==SQLITE_OK ){
+ sqlite3_bind_int64(pSelect, 1, iRowid);
if( SQLITE_ROW==sqlite3_step(pSelect) ){
int i;
int iLangid = langidFromSelect(p, pSelect);
** document pRowid, or false otherwise, and SQLITE_OK is returned. If an
** error occurs, an SQLite error code is returned.
*/
-static int fts3IsEmpty(Fts3Table *p, sqlite3_value *pRowid, int *pisEmpty){
+static int fts3IsEmpty(Fts3Table *p, i64 iRowid, int *pisEmpty){
sqlite3_stmt *pStmt;
int rc;
if( p->zContentTbl ){
*pisEmpty = 0;
rc = SQLITE_OK;
}else{
- rc = fts3SqlStmt(p, SQL_IS_EMPTY, &pStmt, &pRowid);
+ rc = fts3SqlStmt(p, SQL_IS_EMPTY, &pStmt, 0);
if( rc==SQLITE_OK ){
+ sqlite3_bind_int64(pStmt, 1, iRowid);
if( SQLITE_ROW==sqlite3_step(pStmt) ){
*pisEmpty = sqlite3_column_int(pStmt, 0);
}
*/
static int fts3DeleteByRowid(
Fts3Table *p,
- sqlite3_value *pRowid,
+ i64 iRowid,
int *pnChng, /* IN/OUT: Decrement if row is deleted */
u32 *aSzDel
){
int rc = SQLITE_OK; /* Return code */
int bFound = 0; /* True if *pRowid really is in the table */
- fts3DeleteTerms(&rc, p, pRowid, aSzDel, &bFound);
+ fts3DeleteTerms(&rc, p, iRowid, aSzDel, &bFound);
if( bFound && rc==SQLITE_OK ){
int isEmpty = 0; /* Deleting *pRowid leaves the table empty */
- rc = fts3IsEmpty(p, pRowid, &isEmpty);
+ rc = fts3IsEmpty(p, iRowid, &isEmpty);
if( rc==SQLITE_OK ){
if( isEmpty ){
/* Deleting this row means the whole table is empty. In this case
}else{
*pnChng = *pnChng - 1;
if( p->zContentTbl==0 ){
- fts3SqlExec(&rc, p, SQL_DELETE_CONTENT, &pRowid);
+ fts3SqlExecI64(&rc, p, SQL_DELETE_CONTENT, iRowid);
}
if( p->bHasDocsize ){
- fts3SqlExec(&rc, p, SQL_DELETE_DOCSIZE, &pRowid);
+ fts3SqlExecI64(&rc, p, SQL_DELETE_DOCSIZE, iRowid);
}
}
}
return rc;
}
+/*
+** Convert a docid (iDocid) and a language id (iLangid) to a rowid,
+** according to the configured languageid_bits= value belonging to
+** FTS table *p.
+*/
+i64 sqlite3Fts3DocidToRowid(Fts3Table *p, i64 iDocid, int iLangid){
+ i64 iRowid = iDocid;
+ if( p->nLanguageidBits ){
+ iRowid = (iRowid << p->nLanguageidBits) + iLangid;
+ }
+ return iRowid;
+}
+
+i64 sqlite3Fts3RowidToDocid(Fts3Table *p, i64 iRowid){
+ return (iRowid >> p->nLanguageidBits);
+}
+
/*
** This function does the work for the xUpdate method of FTS3 virtual
** tables. The schema of the virtual table being:
** <langid> HIDDEN
** );
**
-**
*/
int sqlite3Fts3UpdateMethod(
sqlite3_vtab *pVtab, /* FTS3 vtab object */
u32 *aSzDel = 0; /* Sizes of deleted documents */
int nChng = 0; /* Net change in number of documents */
int bInsertDone = 0;
+ int iLangid = 0;
assert( p->pSegments==0 );
assert(
goto update_out;
}
- if( nArg>1 && sqlite3_value_int(apVal[2 + p->nColumn + 2])<0 ){
- rc = SQLITE_CONSTRAINT;
- goto update_out;
+ /* If this is an INSERT or UPDATE, check that the new value for the
+ ** languageid is within range. A languageid can never be a negative
+ ** value. If the languageid_bits option was specified when this table
+ ** was created, it must also be less than (2 ^ nLanguageidBits).
+ **
+ ** Also check that if a non-zero languageid_bits value was configured,
+ ** the specified rowid value must be NULL.
+ */
+ if( nArg>1 ){
+ iLangid = sqlite3_value_int(apVal[2 + p->nColumn + 2]);
+ if( iLangid<0 || (p->nLanguageidBits && iLangid>=(1<<p->nLanguageidBits)) ){
+ rc = SQLITE_CONSTRAINT;
+ goto update_out;
+ }
+
+ if( p->nLanguageidBits
+ && sqlite3_value_type(apVal[0])==SQLITE_NULL
+ && sqlite3_value_type(apVal[1])!=SQLITE_NULL
+ ){
+ rc = SQLITE_CONSTRAINT;
+ goto update_out;
+ }
}
/* Allocate space to hold the change in document sizes */
*/
if( nArg>1 && p->zContentTbl==0 ){
/* Find the value object that holds the new rowid value. */
- sqlite3_value *pNewRowid = apVal[3+p->nColumn];
- if( sqlite3_value_type(pNewRowid)==SQLITE_NULL ){
- pNewRowid = apVal[1];
- }
-
- if( sqlite3_value_type(pNewRowid)!=SQLITE_NULL && (
- sqlite3_value_type(apVal[0])==SQLITE_NULL
- || sqlite3_value_int64(apVal[0])!=sqlite3_value_int64(pNewRowid)
- )){
- /* The new rowid is not NULL (in this case the rowid will be
- ** automatically assigned and there is no chance of a conflict), and
- ** the statement is either an INSERT or an UPDATE that modifies the
- ** rowid column. So if the conflict mode is REPLACE, then delete any
- ** existing row with rowid=pNewRowid.
- **
- ** Or, if the conflict mode is not REPLACE, insert the new record into
- ** the %_content table. If we hit the duplicate rowid constraint (or any
- ** other error) while doing so, return immediately.
- **
- ** This branch may also run if pNewRowid contains a value that cannot
- ** be losslessly converted to an integer. In this case, the eventual
- ** call to fts3InsertData() (either just below or further on in this
- ** function) will return SQLITE_MISMATCH. If fts3DeleteByRowid is
- ** invoked, it will delete zero rows (since no row will have
- ** docid=$pNewRowid if $pNewRowid is not an integer value).
- */
- if( sqlite3_vtab_on_conflict(p->db)==SQLITE_REPLACE ){
- rc = fts3DeleteByRowid(p, pNewRowid, &nChng, aSzDel);
- }else{
- rc = fts3InsertData(p, apVal, pRowid);
- bInsertDone = 1;
+ sqlite3_value *pNewDocid = apVal[3+p->nColumn];
+ if( sqlite3_value_type(pNewDocid)==SQLITE_NULL ){
+ if( p->nLanguageidBits ){
+ rc = SQLITE_CONSTRAINT;
+ goto update_out;
+ }
+ pNewDocid = apVal[1];
+ }
+
+ if( sqlite3_value_type(pNewDocid)!=SQLITE_NULL ){
+ int e = sqlite3_value_numeric_type(pNewDocid);
+ i64 iRowid = sqlite3_value_int64(pNewDocid);
+
+ /* Check that the value specified by the user may be losslessly
+ ** converted to an integer. If not, return a "data mismatch" error. */
+ if( (e!=SQLITE_INTEGER)
+ && (e!=SQLITE_FLOAT || (double)iRowid!=sqlite3_value_double(pNewDocid))
+ ){
+ rc = SQLITE_MISMATCH;
+ goto update_out;
+ }
+
+ if( p->nLanguageidBits ){
+ /* Check for an out-of-range docid value. */
+ if( iRowid>=((i64)1 << (63 - p->nLanguageidBits))
+ || iRowid<-1*((i64)1 << (63 - p->nLanguageidBits))
+ ){
+ rc = SQLITE_CONSTRAINT;
+ goto update_out;
+ }
+
+ iRowid = sqlite3Fts3DocidToRowid(p, iRowid, iLangid);
+ }
+
+ if( sqlite3_value_type(apVal[0])==SQLITE_NULL
+ || sqlite3_value_int64(apVal[0])!=iRowid
+ ){
+ /* The new rowid is not NULL (in this case the rowid will be
+ ** automatically assigned and there is no chance of a conflict), and
+ ** the statement is either an INSERT or an UPDATE that modifies the
+ ** rowid column. So if the conflict mode is REPLACE, then delete any
+ ** existing row with rowid=pNewRowid.
+ **
+ ** Or, if the conflict mode is not REPLACE, insert the new record into
+ ** the %_content table. If we hit the duplicate rowid constraint (or
+ ** any other error) while doing so, return immediately.
+ **
+ ** This branch may also run if pNewRowid contains a value that cannot
+ ** be losslessly converted to an integer. In this case, the eventual
+ ** call to fts3InsertData() (either just below or further on in this
+ ** function) will return SQLITE_MISMATCH. If fts3DeleteByRowid is
+ ** invoked, it will delete zero rows (since no row will have
+ ** docid=$pNewRowid if $pNewRowid is not an integer value).
+ */
+ if( sqlite3_vtab_on_conflict(p->db)==SQLITE_REPLACE ){
+ rc = fts3DeleteByRowid(p, iRowid, &nChng, aSzDel);
+ }else{
+ rc = fts3InsertData(p, apVal, 0, iRowid);
+ bInsertDone = 1;
+ *pRowid = iRowid;
+ }
}
}
}
/* If this is a DELETE or UPDATE operation, remove the old record. */
if( sqlite3_value_type(apVal[0])!=SQLITE_NULL ){
assert( sqlite3_value_type(apVal[0])==SQLITE_INTEGER );
- rc = fts3DeleteByRowid(p, apVal[0], &nChng, aSzDel);
+ rc = fts3DeleteByRowid(p, sqlite3_value_int64(apVal[0]), &nChng, aSzDel);
isRemove = 1;
}
/* If this is an INSERT or UPDATE operation, insert the new record. */
if( nArg>1 && rc==SQLITE_OK ){
- int iLangid = sqlite3_value_int(apVal[2 + p->nColumn + 2]);
if( bInsertDone==0 ){
- rc = fts3InsertData(p, apVal, pRowid);
+ rc = fts3InsertData(p, apVal, pRowid, 0);
if( rc==SQLITE_CONSTRAINT && p->zContentTbl==0 ){
rc = FTS_CORRUPT_VTAB;
}
-C Only\sdefault\sHAVE_POSIX_FALLOCATE\son\sfor\slinux,\sand\sthen\sonly\sif\sit\sis\snot\npreviously\sdefined.
-D 2013-06-19T14:49:14.627
+C Add\sthe\slanguageid_bits=\soption\sto\sfts.\sStill\ssome\sproblems\sto\swork\sout.
+D 2013-06-19T20:13:28.631
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in 5e41da95d92656a5004b03d3576e8b226858a28e
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
F ext/fts3/README.syntax a19711dc5458c20734b8e485e75fb1981ec2427a
F ext/fts3/README.tokenizers e0a8b81383ea60d0334d274fadf305ea14a8c314
F ext/fts3/README.txt 8c18f41574404623b76917b9da66fcb0ab38328d
-F ext/fts3/fts3.c 931b3c83abdd1ab3bb389b2130431c2a9ff73b91
+F ext/fts3/fts3.c 2d86f9b356b5e309fa68e20f6891b2ca1e694dc3
F ext/fts3/fts3.h 3a10a0af180d502cecc50df77b1b22df142817fe
-F ext/fts3/fts3Int.h cb4df04cf886d9920a71df9e8faaa5aae2fa48c6
+F ext/fts3/fts3Int.h 9bef3710aa94fc27b117eca41088aa29ed99d4f1
F ext/fts3/fts3_aux.c b02632f6dd0e375ce97870206d914ea6d8df5ccd
F ext/fts3/fts3_expr.c f8eb1046063ba342c7114eba175cabb31c4a64e7
F ext/fts3/fts3_hash.c 8dd2d06b66c72c628c2732555a32bc0943114914
F ext/fts3/fts3_tokenizer1.c 5c98225a53705e5ee34824087478cf477bdb7004
F ext/fts3/fts3_unicode.c 92391b4b4fb043564c6539ea9b8661e3bcba47b9
F ext/fts3/fts3_unicode2.c 0113d3acf13429e6dc38e0647d1bc71211c31a4d
-F ext/fts3/fts3_write.c 6a1fc0e922e76b68e594bf7bc33bac72af9dc47b
+F ext/fts3/fts3_write.c b057e0f72a3d684b812732db0d4a5d9614936db3
F ext/fts3/fts3speed.tcl b54caf6a18d38174f1a6e84219950d85e98bb1e9
F ext/fts3/mkfts3amal.tcl 252ecb7fe6467854f2aa237bf2c390b74e71f100
F ext/fts3/tool/fts3view.c 6cfc5b67a5f0e09c0d698f9fd012c784bfaa9197
F test/fts4check.test 66fa274cab2b615f2fb338b257713aba8fad88a8
F test/fts4content.test 6efc53b4fd03cab167e6998d2b0b7d4b7d419ee6
F test/fts4langid.test 24a6e41063b416bbdf371ff6b4476fa41c194aa7
+F test/fts4langid2.test 50736df3c3adbb2a05d8685802453a16db6c458f
F test/fts4merge.test c424309743fdd203f8e56a1f1cd7872cd66cc0ee
F test/fts4merge2.test 5faa558d1b672f82b847d2a337465fa745e46891
F test/fts4merge3.test aab02a09f50fe6baaddc2e159c3eabc116d45fc7
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh fbc018d67fd7395f440c28f33ef0f94420226381
F tool/win/sqlite.vsix 97894c2790eda7b5bce3cc79cb2a8ec2fde9b3ac
-P b9b30d4f9845d212e2d3206abbf2795099e5d71d
-R 051331ae49dcb9481117487fe2ddd3f7
-U drh
-Z f091211d6e3ac84847a4aa6c11062c5e
+P 2b2ade92788be623af6f57e37d98994be2cec142
+R 1066cd04ca715945e7e0705efe840790
+T *branch * fts-languageid-bits
+T *sym-fts-languageid-bits *
+T -sym-trunk *
+U dan
+Z 9fcd5c1627466406b038b9233ae97f20
--- /dev/null
+# 2012 March 01
+#
+# 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.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library. The
+# focus of this script is testing the languageid=xxx FTS4 option.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set ::testprefix fts4langid2
+
+# If SQLITE_ENABLE_FTS3 is defined, omit this file.
+ifcapable !fts3 {
+ finish_test
+ return
+}
+
+
+#-------------------------------------------------------------------------
+# Test out-of-range values for the languageid_bits= parameter.
+#
+do_catchsql_test 1.1 {
+ CREATE VIRTUAL TABLE t1 USING fts4(languageid=lid, languageid_bits=33);
+} {1 {languageid_bits parameter out of range}}
+
+do_catchsql_test 1.2 {
+ CREATE VIRTUAL TABLE t1 USING fts4(languageid=lid, languageid_bits=-1);
+} {1 {languageid_bits parameter out of range}}
+
+do_catchsql_test 1.3 {
+ CREATE VIRTUAL TABLE t1 USING fts4(languageid=lid, languageid_bits=0);
+ CREATE VIRTUAL TABLE t2 USING fts4(languageid=lid, languageid_bits=32);
+} {0 {}}
+
+do_execsql_test 1.4 {
+ DROP TABLE t1;
+ DROP TABLE t2;
+}
+
+#-------------------------------------------------------------------------
+# Test out-of-range values in the languageid column.
+#
+do_execsql_test 2.1 {
+ CREATE VIRTUAL TABLE t1 USING fts4(languageid=lid, languageid_bits=8);
+ CREATE VIRTUAL TABLE t2 USING fts4(languageid=lid, languageid_bits=7);
+}
+
+do_catchsql_test 2.2 {
+ INSERT INTO t1(docid, lid, content) VALUES(1, 256, 'abc def');
+} {1 {constraint failed}}
+
+do_catchsql_test 2.3 {
+ INSERT INTO t2(docid, lid, content) VALUES(1, 128, 'abc def');
+} {1 {constraint failed}}
+
+do_catchsql_test 2.3 {
+ INSERT INTO t1(docid, lid, content) VALUES(1, -1, 'abc def');
+} {1 {constraint failed}}
+
+do_execsql_test 2.4 {
+ DROP TABLE t1;
+ DROP TABLE t2;
+}
+
+#-------------------------------------------------------------------------
+# Test that if languageid_bits is set to a non-zero value it is
+# not possible to specify a non-NULL rowid, even if it is the same
+# as the docid.
+#
+do_execsql_test 3.1 {
+ CREATE VIRTUAL TABLE t1 USING fts4(languageid=lid, languageid_bits=4);
+ CREATE VIRTUAL TABLE t2 USING fts4(languageid=lid, languageid_bits=0);
+}
+
+do_catchsql_test 3.2.1 {
+ INSERT INTO t1(rowid, lid, content) VALUES(1, 0, 'abc def');
+} {1 {constraint failed}}
+
+do_catchsql_test 3.2.2 {
+ INSERT INTO t2(rowid, lid, content) VALUES(1, 0, 'abc def');
+} {0 {}}
+
+do_catchsql_test 3.3 {
+ INSERT INTO t1(rowid, docid, lid, content) VALUES(2, 2, 0, 'abc def');
+} {1 {constraint failed}}
+
+do_catchsql_test 3.4 {
+ INSERT INTO t1(lid, content) VALUES(0, 'one two def');
+} {1 {constraint failed}}
+
+do_execsql_test 3.4 {
+ DROP TABLE t1;
+ DROP TABLE t2;
+}
+
+#-------------------------------------------------------------------------
+#
+do_execsql_test 4.1 {
+ CREATE VIRTUAL TABLE t1 USING fts4(languageid=lid, languageid_bits=5);
+}
+
+do_execsql_test 4.2 {
+ INSERT INTO t1 (docid, lid, content) VALUES(1, 0, '1 2 3');
+ INSERT INTO t1 (docid, lid, content) VALUES(1, 1, '1 2 3 4');
+}
+
+do_execsql_test 4.3 {
+ SELECT docid, lid FROM t1;
+} {1 0 1 1}
+
+do_execsql_test 4.4 {
+ SELECT docid, lid, content FROM t1 WHERE t1 MATCH '2';
+} {1 0 {1 2 3}}
+
+do_execsql_test 4.5 {
+ SELECT docid, lid, content FROM t1 WHERE t1 MATCH '2' AND lid=1;
+} {1 1 {1 2 3 4}}
+
+breakpoint
+do_execsql_test 4.6 {
+ UPDATE t1 SET content = 'x y z' || lid;
+ SELECT docid, lid FROM t1;
+} {1 0 1 1}
+
+
+finish_test
+