}
/*
-** Close a virtual table handle opened by fts5InitVtab(). If the bDestroy
-** argument is non-zero, attempt delete the shadow tables from teh database
+** Delete a virtual table handle allocated by fts5InitVtab().
*/
-static int fts5FreeVtab(Fts5Table *pTab, int bDestroy){
+static void fts5FreeVtab(Fts5Table *pTab){
int rc = SQLITE_OK;
if( pTab ){
- int rc2;
- rc2 = sqlite3Fts5IndexClose(pTab->pIndex, bDestroy);
- if( rc==SQLITE_OK ) rc = rc2;
- rc2 = sqlite3Fts5StorageClose(pTab->pStorage, bDestroy);
- if( rc==SQLITE_OK ) rc = rc2;
+ sqlite3Fts5IndexClose(pTab->pIndex);
+ sqlite3Fts5StorageClose(pTab->pStorage);
sqlite3Fts5ConfigFree(pTab->pConfig);
sqlite3_free(pTab);
}
** The xDisconnect() virtual table method.
*/
static int fts5DisconnectMethod(sqlite3_vtab *pVtab){
- return fts5FreeVtab((Fts5Table*)pVtab, 0);
+ fts5FreeVtab((Fts5Table*)pVtab);
+ return SQLITE_OK;
}
/*
** The xDestroy() virtual table method.
*/
static int fts5DestroyMethod(sqlite3_vtab *pVtab){
- return fts5FreeVtab((Fts5Table*)pVtab, 1);
+ Fts5Table *pTab = (Fts5Table*)pVtab;
+ int rc = sqlite3Fts5DropAll(pTab->pConfig);
+ if( rc==SQLITE_OK ){
+ fts5FreeVtab((Fts5Table*)pVtab);
+ }
+ return rc;
}
/*
}
if( rc!=SQLITE_OK ){
- fts5FreeVtab(pTab, 0);
+ fts5FreeVtab(pTab);
pTab = 0;
}else if( bCreate ){
fts5CheckTransactionState(pTab, FTS5_BEGIN, 0);
void sqlite3Fts5BufferZero(Fts5Buffer*);
void sqlite3Fts5BufferSet(int*, Fts5Buffer*, int, const u8*);
void sqlite3Fts5BufferAppendPrintf(int *, Fts5Buffer*, char *zFmt, ...);
-void sqlite3Fts5BufferAppendListElem(int*, Fts5Buffer*, const char*, int);
void sqlite3Fts5BufferAppend32(int*, Fts5Buffer*, int);
#define fts5BufferZero(x) sqlite3Fts5BufferZero(x)
};
int sqlite3Fts5PoslistWriterAppend(Fts5Buffer*, Fts5PoslistWriter*, i64);
-int sqlite3Fts5PoslistNext(
- const u8 *a, int n, /* Buffer containing poslist */
- int *pi, /* IN/OUT: Offset within a[] */
- int *piCol, /* IN/OUT: Current column */
- int *piOff /* IN/OUT: Current token offset */
-);
-
int sqlite3Fts5PoslistNext64(
const u8 *a, int n, /* Buffer containing poslist */
int *pi, /* IN/OUT: Offset within a[] */
** Create/destroy an Fts5Index object.
*/
int sqlite3Fts5IndexOpen(Fts5Config *pConfig, int bCreate, Fts5Index**, char**);
-int sqlite3Fts5IndexClose(Fts5Index *p, int bDestroy);
+int sqlite3Fts5IndexClose(Fts5Index *p);
/*
** for(
typedef struct Fts5Storage Fts5Storage;
int sqlite3Fts5StorageOpen(Fts5Config*, Fts5Index*, int, Fts5Storage**, char**);
-int sqlite3Fts5StorageClose(Fts5Storage *p, int bDestroy);
+int sqlite3Fts5StorageClose(Fts5Storage *p);
-int sqlite3Fts5DropTable(Fts5Config*, const char *zPost);
+int sqlite3Fts5DropAll(Fts5Config*);
int sqlite3Fts5CreateTable(Fts5Config*, const char*, const char*, int, char **);
int sqlite3Fts5StorageDelete(Fts5Storage *p, i64);
const char *zStr
){
int nStr = strlen(zStr);
- if( sqlite3Fts5BufferGrow(pRc, pBuf, nStr+1) ) return;
- sqlite3Fts5BufferAppendBlob(pRc, pBuf, nStr, (const u8*)zStr);
- if( *pRc==SQLITE_OK ) pBuf->p[pBuf->n] = 0x00;
+ sqlite3Fts5BufferAppendBlob(pRc, pBuf, nStr+1, (const u8*)zStr);
+ pBuf->n--;
}
/*
return rc;
}
-int sqlite3Fts5PoslistNext(
- const u8 *a, int n, /* Buffer containing poslist */
- int *pi, /* IN/OUT: Offset within a[] */
- int *piCol, /* IN/OUT: Current column */
- int *piOff /* IN/OUT: Current token offset */
-){
- int i = *pi;
- int iVal;
- if( i>=n ){
- /* EOF */
- return 1;
- }
- i += getVarint32(&a[i], iVal);
- if( iVal==1 ){
- i += getVarint32(&a[i], iVal);
- *piCol = iVal;
- *piOff = 0;
- i += getVarint32(&a[i], iVal);
- }
- *piOff += (iVal-2);
- *pi = i;
- return 0;
-}
-
-void sqlite3Fts5BufferAppendListElem(
- int *pRc, /* IN/OUT: Error code */
- Fts5Buffer *pBuf, /* Buffer to append to */
- const char *z, int n /* Value to append to buffer */
-){
- int bParen = (n==0);
- int nMax = n*2 + 2 + 1;
- u8 *pOut;
- int i;
-
- /* Ensure the buffer has space for the new list element */
- if( sqlite3Fts5BufferGrow(pRc, pBuf, nMax) ) return;
- pOut = &pBuf->p[pBuf->n];
-
- /* Figure out if we need the enclosing {} */
- for(i=0; i<n && bParen==0; i++){
- if( z[i]=='"' || z[i]==' ' ){
- bParen = 1;
- }
- }
-
- if( bParen ) *pOut++ = '{';
- for(i=0; i<n; i++){
- *pOut++ = z[i];
- }
- if( bParen ) *pOut++ = '}';
-
- pBuf->n = pOut - pBuf->p;
- *pOut = '\0';
-}
-
void *sqlite3Fts5MallocZero(int *pRc, int nByte){
void *pRet = 0;
if( *pRc==SQLITE_OK ){
}
void sqlite3Fts5HashScanNext(Fts5Hash *p){
- Fts5HashEntry *pScan = p->pScan;
- if( pScan ) p->pScan = pScan->pScanNext;
+ assert( !sqlite3Fts5HashScanEof(p) );
+ p->pScan = p->pScan->pScanNext;
}
int sqlite3Fts5HashScanEof(Fts5Hash *p){
** the Fts5Index handle passed as the first argument.
*/
static void *fts5IdxMalloc(Fts5Index *p, int nByte){
- void *pRet = 0;
- if( p->rc==SQLITE_OK ){
- pRet = sqlite3_malloc(nByte);
- if( pRet==0 ){
- p->rc = SQLITE_NOMEM;
- }else{
- memset(pRet, 0, nByte);
- }
- }
- return pRet;
+ return sqlite3Fts5MallocZero(&p->rc, nByte);
}
/*
int rc = SQLITE_OK;
Fts5Index *p; /* New object */
- *pp = p = (Fts5Index*)sqlite3_malloc(sizeof(Fts5Index));
- if( !p ) return SQLITE_NOMEM;
-
- memset(p, 0, sizeof(Fts5Index));
- p->pConfig = pConfig;
- p->nWorkUnit = FTS5_WORK_UNIT;
- p->nMaxPendingData = 1024*1024;
- p->zDataTbl = sqlite3_mprintf("%s_data", pConfig->zName);
- if( p->zDataTbl==0 ){
- rc = SQLITE_NOMEM;
- }else if( bCreate ){
- rc = sqlite3Fts5CreateTable(
- pConfig, "data", "id INTEGER PRIMARY KEY, block BLOB", 0, pzErr
- );
- if( rc==SQLITE_OK ){
- rc = sqlite3Fts5IndexReinit(p);
+ *pp = p = (Fts5Index*)sqlite3Fts5MallocZero(&rc, sizeof(Fts5Index));
+ if( rc==SQLITE_OK ){
+ p->pConfig = pConfig;
+ p->nWorkUnit = FTS5_WORK_UNIT;
+ p->nMaxPendingData = 1024*1024;
+ p->zDataTbl = sqlite3_mprintf("%s_data", pConfig->zName);
+ if( p->zDataTbl==0 ){
+ rc = SQLITE_NOMEM;
+ }else if( bCreate ){
+ rc = sqlite3Fts5CreateTable(
+ pConfig, "data", "id INTEGER PRIMARY KEY, block BLOB", 0, pzErr
+ );
+ if( rc==SQLITE_OK ){
+ rc = sqlite3Fts5IndexReinit(p);
+ }
}
}
- assert( p->rc==SQLITE_OK || rc!=SQLITE_OK );
+ assert( rc!=SQLITE_OK || p->rc==SQLITE_OK );
if( rc ){
- sqlite3Fts5IndexClose(p, 0);
+ sqlite3Fts5IndexClose(p);
*pp = 0;
}
return rc;
/*
** Close a handle opened by an earlier call to sqlite3Fts5IndexOpen().
*/
-int sqlite3Fts5IndexClose(Fts5Index *p, int bDestroy){
+int sqlite3Fts5IndexClose(Fts5Index *p){
int rc = SQLITE_OK;
if( p ){
- if( bDestroy ){
- rc = sqlite3Fts5DropTable(p->pConfig, "data");
- }
assert( p->pReader==0 );
sqlite3_finalize(p->pWriter);
sqlite3_finalize(p->pDeleter);
}
/*
-** Drop the shadow table with the postfix zPost (e.g. "content"). Return
-** SQLITE_OK if successful or an SQLite error code otherwise.
+** Drop all shadow tables. Return SQLITE_OK if successful or an SQLite error
+** code otherwise.
*/
-int sqlite3Fts5DropTable(Fts5Config *pConfig, const char *zPost){
- return fts5ExecPrintf(pConfig->db, 0, "DROP TABLE IF EXISTS %Q.'%q_%q'",
- pConfig->zDb, pConfig->zName, zPost
+int sqlite3Fts5DropAll(Fts5Config *pConfig){
+ int rc = fts5ExecPrintf(pConfig->db, 0,
+ "DROP TABLE IF EXISTS %Q.'%q_data';"
+ "DROP TABLE IF EXISTS %Q.'%q_docsize';"
+ "DROP TABLE IF EXISTS %Q.'%q_config';",
+ pConfig->zDb, pConfig->zName,
+ pConfig->zDb, pConfig->zName,
+ pConfig->zDb, pConfig->zName
);
+ if( rc==SQLITE_OK && pConfig->eContent==FTS5_CONTENT_NORMAL ){
+ rc = fts5ExecPrintf(pConfig->db, 0,
+ "DROP TABLE IF EXISTS %Q.'%q_content';",
+ pConfig->zDb, pConfig->zName
+ );
+ }
+ return rc;
}
/*
}
if( rc ){
- sqlite3Fts5StorageClose(p, 0);
+ sqlite3Fts5StorageClose(p);
*pp = 0;
}
return rc;
/*
** Close a handle opened by an earlier call to sqlite3Fts5StorageOpen().
*/
-int sqlite3Fts5StorageClose(Fts5Storage *p, int bDestroy){
+int sqlite3Fts5StorageClose(Fts5Storage *p){
int rc = SQLITE_OK;
if( p ){
int i;
sqlite3_finalize(p->aStmt[i]);
}
- /* If required, remove the shadow tables from the database */
- if( bDestroy ){
- if( p->pConfig->eContent==FTS5_CONTENT_NORMAL ){
- rc = sqlite3Fts5DropTable(p->pConfig, "content");
- }
- if( rc==SQLITE_OK ) rc = sqlite3Fts5DropTable(p->pConfig, "docsize");
- if( rc==SQLITE_OK ) rc = sqlite3Fts5DropTable(p->pConfig, "config");
- }
-
sqlite3_free(p);
}
return rc;
return TCL_OK;
}
+
+static unsigned int f5t_fts5HashKey(int nSlot, const char *p, int n){
+ int i;
+ unsigned int h = 13;
+ for(i=n-1; i>=0; i--){
+ h = (h << 3) ^ h ^ p[i];
+ }
+ return (h % nSlot);
+}
+
+static int f5tTokenHash(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ int bOld = sqlite3_fts5_may_be_corrupt;
+ char *z;
+ int n;
+ unsigned int iVal;
+ int nSlot;
+
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "NSLOT TOKEN");
+ return TCL_ERROR;
+ }
+ if( Tcl_GetIntFromObj(interp, objv[1], &nSlot) ){
+ return TCL_ERROR;
+ }
+ z = Tcl_GetStringFromObj(objv[2], &n);
+
+ iVal = f5t_fts5HashKey(nSlot, z, n);
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(iVal));
+ return TCL_OK;
+}
+
/*
** Entry point.
*/
{ "sqlite3_fts5_token", f5tTokenizerReturn, 1 },
{ "sqlite3_fts5_tokenize", f5tTokenize, 0 },
{ "sqlite3_fts5_create_function", f5tCreateFunction, 0 },
- { "sqlite3_fts5_may_be_corrupt", f5tMayBeCorrupt, 0 }
+ { "sqlite3_fts5_may_be_corrupt", f5tMayBeCorrupt, 0 },
+ { "sqlite3_fts5_token_hash", f5tTokenHash, 0 }
};
int i;
F5tTokenizerContext *pContext;
faultsim_test_result [list 0 {}]
}
+#-------------------------------------------------------------------------
+# An OOM while flushing an unusually large term to disk.
+#
+reset_db
+do_execsql_test 3.0 {
+ CREATE VIRTUAL TABLE xx USING fts5(x);
+}
+faultsim_save_and_close
+
+set doc [fts5_rnddoc 1000]
+do_faultsim_test 3.1 -faults oom-* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql { INSERT INTO xx(x) VALUES ($::doc) }
+} -test {
+ faultsim_test_result [list 0 {}]
+}
+
+set doc [string repeat "abc " 100]
+do_faultsim_test 3.2 -faults oom-* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql { INSERT INTO xx(x) VALUES ($::doc) }
+} -test {
+ faultsim_test_result [list 0 {}]
+}
--- /dev/null
+# 2014 June 17
+#
+# 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 is focused on OOM errors.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+source $testdir/malloc_common.tcl
+set testprefix fts5fault4
+
+# If SQLITE_ENABLE_FTS3 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+#-------------------------------------------------------------------------
+# An OOM while dropping an fts5 table.
+#
+db func rnddoc fts5_rnddoc
+do_test 1.0 {
+ execsql { CREATE VIRTUAL TABLE xx USING fts5(x) }
+} {}
+faultsim_save_and_close
+
+do_faultsim_test 1 -faults oom-* -prep {
+ faultsim_restore_and_reopen
+ execsql { SELECT * FROM xx }
+} -body {
+ execsql { DROP TABLE xx }
+} -test {
+ faultsim_test_result [list 0 {}]
+}
+
+
+finish_test
+
--- /dev/null
+# 2015 April 21
+#
+# 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.
+#
+#***********************************************************************
+#
+# The tests in this file are focused on the code in fts5_hash.c.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5hash
+
+#-------------------------------------------------------------------------
+# Return a list of tokens (a vocabulary) that all share the same hash
+# key value. This can be used to test hash collisions.
+#
+proc build_vocab1 {args} {
+
+ set O(-nslot) 1024
+ set O(-nword) 20
+ set O(-hash) 88
+ set O(-prefix) ""
+
+ if {[llength $args] % 2} { error "bad args" }
+ array set O2 $args
+ foreach {k v} $args {
+ if {[info exists O($k)]==0} { error "bad option: $k" }
+ set O($k) $v
+ }
+
+ set L [list]
+ while {[llength $L] < $O(-nword)} {
+ set t "$O(-prefix)[random_token]"
+ set h [sqlite3_fts5_token_hash $O(-nslot) $t]
+ if {$O(-hash)==$h} { lappend L $t }
+ }
+ return $L
+}
+
+proc random_token {} {
+ set map [list 0 a 1 b 2 c 3 d 4 e 5 f 6 g 7 h 8 i 9 j]
+ set iVal [expr int(rand() * 2000000)]
+ return [string map $map $iVal]
+}
+
+proc random_doc {vocab nWord} {
+ set doc ""
+ set nVocab [llength $vocab]
+ for {set i 0} {$i<$nWord} {incr i} {
+ set j [expr {int(rand() * $nVocab)}]
+ lappend doc [lindex $vocab $j]
+ }
+ return $doc
+}
+
+set vocab [build_vocab1]
+db func r random_doc
+
+do_execsql_test 1.0 {
+ CREATE VIRTUAL TABLE eee USING fts5(e, ee);
+ BEGIN;
+ WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<100)
+ INSERT INTO eee SELECT r($vocab, 5), r($vocab, 7) FROM ii;
+ INSERT INTO eee(eee) VALUES('integrity-check');
+ COMMIT;
+ INSERT INTO eee(eee) VALUES('integrity-check');
+}
+
+set hash [sqlite3_fts5_token_hash 1024 xyz]
+set vocab [build_vocab1 -prefix xyz -hash $hash]
+lappend vocab xyz
+
+do_execsql_test 1.1 {
+ BEGIN;
+ WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<100)
+ INSERT INTO eee SELECT r($vocab, 5), r($vocab, 7) FROM ii;
+ INSERT INTO eee(eee) VALUES('integrity-check');
+ COMMIT;
+ INSERT INTO eee(eee) VALUES('integrity-check');
+}
+
+
+finish_test
+
#
#***********************************************************************
#
+# This file containst tests focused on prefix indexes.
#
source [file join [file dirname [info script]] fts5_common.tcl]
-C Change\sthe\sfts5\scontent=\soption\sso\sthat\sit\smatches\sfts5\scolumns\swith\sthe\sunderlying\stable\scolumns\sby\sname,\snot\sby\stheir\sposition\swithin\sthe\sCREATE\sTABLE\sstatement.
-D 2015-04-27T16:21:49.481
+C Improve\scoverage\sof\sfts5\stests.
+D 2015-04-28T18:35:28.633
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in 31b38b9da2e4b36f54a013bd71a5c3f6e45ca78f
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
F ext/fts3/unicode/UnicodeData.txt cd07314edb62d49fde34debdaf92fa2aa69011e7
F ext/fts3/unicode/mkunicode.tcl 159c1194da0bc72f51b3c2eb71022568006dc5ad
F ext/fts5/extract_api_docs.tcl 55a6d648d516f35d9a1e580ac00de27154e1904a
-F ext/fts5/fts5.c 3383b8a44766c68bda812b68ce74684c6b87787f
+F ext/fts5/fts5.c d9a99a595c0e341cb24918bc67c323d2444a3036
F ext/fts5/fts5.h 24a2cc35b5e76eec57b37ba48c12d9d2cb522b3a
-F ext/fts5/fts5Int.h d148c951deae924105d77f21f25287b60c57327a
+F ext/fts5/fts5Int.h f573fe6c50471f1d66682fce282da801009c54e1
F ext/fts5/fts5_aux.c fcea18b1a2a3f95a498b52aba2983557d7678a22
-F ext/fts5/fts5_buffer.c 3ba56cc6824c9f7b1e0695159e0a9c636f6b4a23
+F ext/fts5/fts5_buffer.c 8c8cfe7f09ca2767ab53ea883f9a0af0edb6bbae
F ext/fts5/fts5_config.c f344ffa24d2add70fd5bde2b73c44846ad7a06bd
F ext/fts5/fts5_expr.c 05da381ab26031243266069302c6eb4094b2c5dd
-F ext/fts5/fts5_hash.c 3cb5a3d04dd2030eb0ac8d544711dfd37c0e6529
-F ext/fts5/fts5_index.c 65d5a75b1ba5f6db9f283f91e71aaa14105dcef7
-F ext/fts5/fts5_storage.c d5c3567b31a0e334ac7d4ac67a2be1c6ae9165cd
-F ext/fts5/fts5_tcl.c 10bf0eb678d34c1bfdcfaf653d2e6dd92afa8b38
+F ext/fts5/fts5_hash.c 29d8b0668727863cc1f1efa65efe4dd78635b016
+F ext/fts5/fts5_index.c de588982b0237b1605d6c37afd115b34c95c3da1
+F ext/fts5/fts5_storage.c ef60fc9dcc4e274f9589165e26833173c273ae18
+F ext/fts5/fts5_tcl.c af1d37fa93bcabc926aa4e89500adedbbe84a520
F ext/fts5/fts5_tokenize.c c07f2c2f749282c1dbbf46bde1f6d7095c740b8b
F ext/fts5/fts5_unicode2.c f74f53316377068812a1fa5a37819e6b8124631d
F ext/fts5/fts5parse.y 777da8e5819f75c217982c79c29d014c293acac9
F ext/fts5/test/fts5eb.test 728a1f23f263548f5c29b29dfb851b5f2dbe723e
F ext/fts5/test/fts5fault1.test ed71717a479bef32d05f02d9c48691011d160d4d
F ext/fts5/test/fts5fault2.test 26c3d70648f691e2cc9391e14bbc11a973656383
-F ext/fts5/test/fts5fault3.test f8935b92976ae645d43205562fdbb0c8511dd049
+F ext/fts5/test/fts5fault3.test d6e9577d4312e331a913c72931bf131704efc8f3
+F ext/fts5/test/fts5fault4.test e860e0cf7e56f2f87330023be1f1ced44128d5c8
F ext/fts5/test/fts5full.test 0924bdca5416a242103239ace79c6f5aa34bab8d
+F ext/fts5/test/fts5hash.test adb7b0442cc1c77c507f07e16d11490486e75dfa
F ext/fts5/test/fts5merge.test 453a0717881aa7784885217b2040f3f275caff03
F ext/fts5/test/fts5near.test 3f9f64e16cac82725d03d4e04c661090f0b3b947
F ext/fts5/test/fts5optimize.test 0028c90a7817d3e576d1148fc8dff17d89054e54
F ext/fts5/test/fts5porter.test 50322599823cb8080a99f0ec0c39f7d0c12bcb5e
-F ext/fts5/test/fts5prefix.test 4610dfba4460d92f23a8014874a46493f1be77b5
+F ext/fts5/test/fts5prefix.test 1287803c3df0e43f536196256fb9e0e6baccb4f1
F ext/fts5/test/fts5rebuild.test ee6792715c6c528cc188e7869d67c3c655889ddb
F ext/fts5/test/fts5rowid.test a1b2a6d76648c734c1aab11ee1a619067e8d90e6
F ext/fts5/test/fts5tokenizer.test 7a6ee24db908c09a0dc1eba634ffa17afcc05d86
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P ffeb3ef3cfec3681b72bb28cfa612aa15e07887d
-R 16d25c5b756f9ed2c991a70239bfd1f6
+P e38e2bb637844dae8ae5d5f3e23d8369e1b91e45
+R 36584b17863acf9c4ced66420a8dd86e
U dan
-Z e5577dade0e18372482b97b5f2daae05
+Z 4aeb27eb1cd105a6fffeab8d10b6e855
-e38e2bb637844dae8ae5d5f3e23d8369e1b91e45
\ No newline at end of file
+8e8136f2dc08082c2984462719d9cba0f212c92a
\ No newline at end of file