static int fts5SpecialInsert(
Fts5Table *pTab, /* Fts5 table object */
sqlite3_value *pCmd, /* Value inserted into special column */
- sqlite3_value *pVal /* Value inserted into rowid column */
+ sqlite3_value *pVal /* Value inserted into rank column */
){
Fts5Config *pConfig = pTab->pConfig;
const char *z = (const char*)sqlite3_value_text(pCmd);
}
}else if( 0==sqlite3_stricmp("optimize", z) ){
rc = sqlite3Fts5StorageOptimize(pTab->pStorage);
+ }else if( 0==sqlite3_stricmp("merge", z) ){
+ int nMerge = sqlite3_value_int(pVal);
+ rc = sqlite3Fts5StorageMerge(pTab->pStorage, nMerge);
}else if( 0==sqlite3_stricmp("integrity-check", z) ){
rc = sqlite3Fts5StorageIntegrity(pTab->pStorage);
}else{
int sqlite3Fts5IndexReinit(Fts5Index *p);
int sqlite3Fts5IndexOptimize(Fts5Index *p);
+int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge);
int sqlite3Fts5IndexLoadConfig(Fts5Index *p);
int sqlite3Fts5StorageDeleteAll(Fts5Storage *p);
int sqlite3Fts5StorageRebuild(Fts5Storage *p);
int sqlite3Fts5StorageOptimize(Fts5Storage *p);
+int sqlite3Fts5StorageMerge(Fts5Storage *p, int nMerge);
/*
** End of interface to code in fts5_storage.c.
/* This call may return SQLITE_ABORT if there has been a savepoint
** rollback since it was last used. In this case a new blob handle
** is required. */
- rc = sqlite3_blob_reopen(p->pReader, iRowid);
- if( rc==SQLITE_ABORT ){
+ sqlite3_blob *pBlob = p->pReader;
+ p->pReader = 0;
+ rc = sqlite3_blob_reopen(pBlob, iRowid);
+ assert( p->pReader==0 );
+ p->pReader = pBlob;
+ if( rc!=SQLITE_OK ){
fts5CloseReader(p);
- rc = SQLITE_OK;
}
+ if( rc==SQLITE_ABORT ) rc = SQLITE_OK;
}
/* If the blob handle is not yet open, open and seek it. Otherwise, use
** the blob_reopen() API to reseek the existing blob handle. */
- if( p->pReader==0 ){
+ if( p->pReader==0 && rc==SQLITE_OK ){
Fts5Config *pConfig = p->pConfig;
rc = sqlite3_blob_open(pConfig->db,
pConfig->zDb, p->zDataTbl, "block", iRowid, 0, &p->pReader
** returned in this case.
*/
static int fts5AllocateSegid(Fts5Index *p, Fts5Structure *pStruct){
- int i;
u32 iSegid = 0;
if( p->rc==SQLITE_OK ){
pWriter->iIdx = iIdx;
pWriter->iSegid = pSeg->iSegid;
pWriter->aWriter = (Fts5PageWriter*)fts5IdxMalloc(p, nByte);
- pWriter->nWriter = pSeg->nHeight;
if( p->rc==SQLITE_OK ){
int pgno = 1;
int i;
+ pWriter->nWriter = pSeg->nHeight;
pWriter->aWriter[0].pgno = pSeg->pgnoLast+1;
for(i=pSeg->nHeight-1; i>0; i--){
i64 iRowid = FTS5_SEGMENT_ROWID(pWriter->iIdx, pWriter->iSegid, i, pgno);
if( pSeg->nHeight==1 ){
pWriter->nEmpty = pSeg->pgnoLast-1;
}
- assert( (pgno+pWriter->nEmpty)==pSeg->pgnoLast );
+ assert( p->rc!=SQLITE_OK || (pgno+pWriter->nEmpty)==pSeg->pgnoLast );
pWriter->bFirstTermInPage = 1;
assert( pWriter->aWriter[0].term.n==0 );
}
fts5WriteInit(p, &writer, iIdx, iSegid);
/* Add the new segment to the output level */
- if( iLvl+1==pStruct->nLevel ) pStruct->nLevel++;
pSeg = &pLvlOut->aSeg[pLvlOut->nSeg];
pLvlOut->nSeg++;
pSeg->pgnoFirst = 1;
if( pnRem ) *pnRem -= writer.nLeafWritten;
}
+/*
+** Do up to nPg pages of automerge work on index iIdx.
+*/
+static void fts5IndexMerge(
+ Fts5Index *p, /* FTS5 backend object */
+ int iIdx, /* Index to work on */
+ Fts5Structure **ppStruct, /* IN/OUT: Current structure of index */
+ int nPg /* Pages of work to do */
+){
+ int nRem = nPg;
+ Fts5Structure *pStruct = *ppStruct;
+ while( nRem>0 && p->rc==SQLITE_OK ){
+ int iLvl; /* To iterate through levels */
+ int iBestLvl = 0; /* Level offering the most input segments */
+ int nBest = 0; /* Number of input segments on best level */
+
+ /* Set iBestLvl to the level to read input segments from. */
+ assert( pStruct->nLevel>0 );
+ for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
+ Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl];
+ if( pLvl->nMerge ){
+ if( pLvl->nMerge>nBest ){
+ iBestLvl = iLvl;
+ nBest = pLvl->nMerge;
+ }
+ break;
+ }
+ if( pLvl->nSeg>nBest ){
+ nBest = pLvl->nSeg;
+ iBestLvl = iLvl;
+ }
+ }
+
+ /* If nBest is still 0, then the index must be empty. */
+#ifdef SQLITE_DEBUG
+ for(iLvl=0; nBest==0 && iLvl<pStruct->nLevel; iLvl++){
+ assert( pStruct->aLevel[iLvl].nSeg==0 );
+ }
+#endif
+
+ if( nBest<p->pConfig->nAutomerge
+ && pStruct->aLevel[iBestLvl].nMerge==0
+ ){
+ break;
+ }
+ fts5IndexMergeLevel(p, iIdx, &pStruct, iBestLvl, &nRem);
+ if( p->rc==SQLITE_OK && pStruct->aLevel[iBestLvl].nMerge==0 ){
+ fts5StructurePromote(p, iBestLvl+1, pStruct);
+ }
+ }
+ *ppStruct = pStruct;
+}
+
/*
** A total of nLeaf leaf pages of data has just been flushed to a level-0
** segments in index iIdx with structure pStruct. This function updates the
** If an error occurs, set the Fts5Index.rc error code. If an error has
** already occurred, this function is a no-op.
*/
-static void fts5IndexWork(
+static void fts5IndexAutomerge(
Fts5Index *p, /* FTS5 backend object */
int iIdx, /* Index to work on */
Fts5Structure **ppStruct, /* IN/OUT: Current structure of index */
int nLeaf /* Number of output leaves just written */
){
- if( p->rc==SQLITE_OK ){
+ if( p->rc==SQLITE_OK && p->pConfig->nAutomerge>0 ){
Fts5Structure *pStruct = *ppStruct;
i64 nWrite; /* Initial value of write-counter */
int nWork; /* Number of work-quanta to perform */
pStruct->nWriteCounter += nLeaf;
nRem = p->nWorkUnit * nWork * pStruct->nLevel;
- while( nRem>0 ){
- int iLvl; /* To iterate through levels */
- int iBestLvl = 0; /* Level offering the most input segments */
- int nBest = 0; /* Number of input segments on best level */
-
- /* Set iBestLvl to the level to read input segments from. */
- assert( pStruct->nLevel>0 );
- for(iLvl=0; iLvl<pStruct->nLevel; iLvl++){
- Fts5StructureLevel *pLvl = &pStruct->aLevel[iLvl];
- if( pLvl->nMerge ){
- if( pLvl->nMerge>nBest ){
- iBestLvl = iLvl;
- nBest = pLvl->nMerge;
- }
- break;
- }
- if( pLvl->nSeg>nBest ){
- nBest = pLvl->nSeg;
- iBestLvl = iLvl;
- }
- }
-
- /* If nBest is still 0, then the index must be empty. */
-#ifdef SQLITE_DEBUG
- for(iLvl=0; nBest==0 && iLvl<pStruct->nLevel; iLvl++){
- assert( pStruct->aLevel[iLvl].nSeg==0 );
- }
-#endif
-
- if( nBest<p->pConfig->nAutomerge
- && pStruct->aLevel[iBestLvl].nMerge==0
- ){
- break;
- }
- fts5IndexMergeLevel(p, iIdx, &pStruct, iBestLvl, &nRem);
- assert( nRem==0 || p->rc==SQLITE_OK );
- if( p->rc==SQLITE_OK && pStruct->aLevel[iBestLvl].nMerge==0 ){
- fts5StructurePromote(p, iBestLvl+1, pStruct);
- }
- *ppStruct = pStruct;
- }
-
+ fts5IndexMerge(p, iIdx, ppStruct, nRem);
}
}
-static void fts5IndexCrisisMerge(
+static void fts5IndexCrisismerge(
Fts5Index *p, /* FTS5 backend object */
int iIdx, /* Index to work on */
Fts5Structure **ppStruct /* IN/OUT: Current structure of index */
){
+ const int nCrisis = p->pConfig->nCrisisMerge;
Fts5Structure *pStruct = *ppStruct;
int iLvl = 0;
- while( p->rc==SQLITE_OK
- && iLvl<pStruct->nLevel
- && pStruct->aLevel[iLvl].nSeg>=p->pConfig->nCrisisMerge
- ){
+
+ assert( p->rc!=SQLITE_OK || pStruct->nLevel>0 );
+ while( p->rc==SQLITE_OK && pStruct->aLevel[iLvl].nSeg>=nCrisis ){
fts5IndexMergeLevel(p, iIdx, &pStruct, iLvl, 0);
fts5StructurePromote(p, iLvl+1, pStruct);
iLvl++;
}
- if( p->pConfig->nAutomerge>0 ) fts5IndexWork(p, iHash, &pStruct, pgnoLast);
- fts5IndexCrisisMerge(p, iHash, &pStruct);
+ fts5IndexAutomerge(p, iHash, &pStruct, pgnoLast);
+ fts5IndexCrisismerge(p, iHash, &pStruct);
fts5StructureWrite(p, iHash, pStruct);
fts5StructureRelease(pStruct);
}
int nLeaf = 0; /* Number of leaves written */
/* If an error has already occured this call is a no-op. */
- if( p->rc!=SQLITE_OK || p->nPendingData==0 ) return;
+ if( p->nPendingData==0 ) return;
assert( p->apHash );
/* Flush the terms and each prefix index to disk */
Fts5Config *pConfig = p->pConfig;
int i;
+ assert( p->rc==SQLITE_OK );
fts5IndexFlush(p);
for(i=0; i<=pConfig->nPrefix; i++){
Fts5Structure *pStruct = fts5StructureRead(p, i);
return fts5IndexReturn(p);
}
+int sqlite3Fts5IndexMerge(Fts5Index *p, int nMerge){
+ Fts5Structure *pStruct;
+
+ pStruct = fts5StructureRead(p, 0);
+ fts5IndexMerge(p, 0, &pStruct, nMerge);
+ fts5StructureWrite(p, 0, pStruct);
+ fts5StructureRelease(pStruct);
+
+ return fts5IndexReturn(p);
+}
/*
}
if( iRowid<=p->iWriteRowid || (p->nPendingData > p->nMaxPendingData) ){
+ assert( p->rc==SQLITE_OK );
fts5IndexFlush(p);
}
p->iWriteRowid = iRowid;
){
Fts5BtreeIter iter; /* Used to iterate through b-tree hierarchy */
+ if( pSeg->pgnoFirst==0 && pSeg->pgnoLast==0 ) return;
+
/* Iterate through the b-tree hierarchy. */
for(fts5BtreeIterInit(p, iIdx, pSeg, &iter);
p->rc==SQLITE_OK && iter.bEof==0;
return sqlite3Fts5IndexOptimize(p->pIndex);
}
+int sqlite3Fts5StorageMerge(Fts5Storage *p, int nMerge){
+ return sqlite3Fts5IndexMerge(p->pIndex, nMerge);
+}
+
/*
** Allocate a new rowid. This is used for "external content" tables when
** a NULL value is inserted into the rowid column. The new rowid is allocated
return
}
-if 0 {
-
set doc [string trim [string repeat "x y z " 200]]
do_execsql_test 1.0 {
CREATE TABLE t1(a INTEGER PRIMARY KEY, x);
catchsql { ROLLBACK }
}
-}
-
#-------------------------------------------------------------------------
# OOM within an 'optimize' operation that writes multiple pages to disk.
#
--- /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 fts5fault3
+
+# If SQLITE_ENABLE_FTS3 is defined, omit this file.
+ifcapable !fts5 {
+ finish_test
+ return
+}
+
+#-------------------------------------------------------------------------
+# An OOM while resuming a partially completed segment merge.
+#
+db func rnddoc fts5_rnddoc
+do_test 1.0 {
+ expr srand(0)
+ execsql {
+ CREATE VIRTUAL TABLE xx USING fts5(x);
+ INSERT INTO xx(xx, rank) VALUES('pgsz', 32);
+ INSERT INTO xx(xx, rank) VALUES('automerge', 16);
+ }
+ for {set i 0} {$i < 10} {incr i} {
+ execsql {
+ BEGIN;
+ INSERT INTO xx(x) VALUES(rnddoc(20));
+ INSERT INTO xx(x) VALUES(rnddoc(20));
+ INSERT INTO xx(x) VALUES(rnddoc(20));
+ COMMIT
+ }
+ }
+
+ execsql {
+ INSERT INTO xx(xx, rank) VALUES('automerge', 2);
+ INSERT INTO xx(xx, rank) VALUES('merge', 50);
+ }
+} {}
+faultsim_save_and_close
+
+do_faultsim_test 1 -faults oom-* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql { INSERT INTO xx(xx, rank) VALUES('merge', 1) }
+} -test {
+ faultsim_test_result [list 0 {}]
+}
+
+#-------------------------------------------------------------------------
+# An OOM while flushing an unusually large term to disk.
+#
+reset_db
+do_execsql_test 2.0 {
+ CREATE VIRTUAL TABLE xx USING fts5(x);
+ INSERT INTO xx(xx, rank) VALUES('pgsz', 32);
+}
+faultsim_save_and_close
+
+set doc "a long term abcdefghijklmnopqrstuvwxyz "
+append doc "and then abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz "
+append doc [string repeat "abcdefghijklmnopqrstuvwxyz" 10]
+
+do_faultsim_test 2 -faults oom-* -prep {
+ faultsim_restore_and_reopen
+} -body {
+ execsql { INSERT INTO xx(x) VALUES ($::doc) }
+} -test {
+ faultsim_test_result [list 0 {}]
+}
+
+
+
+
+finish_test
+
--- /dev/null
+# 2014 Dec 20
+#
+# 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.
+#
+#***********************************************************************
+#
+# Test that focus on incremental merges of segments.
+#
+
+source [file join [file dirname [info script]] fts5_common.tcl]
+set testprefix fts5merge
+
+db func repeat [list string repeat]
+
+#-------------------------------------------------------------------------
+# Create an fts index so that:
+#
+# * the index consists of two top-level segments
+# * each segment contains records related to $nRowPerSeg rows
+# * all rows consist of tokens "x" and "y" only.
+#
+# Then run ('merge', 1) until everything is completely merged.
+#
+proc do_merge1_test {testname nRowPerSeg} {
+ set ::nRowPerSeg [expr $nRowPerSeg]
+ do_execsql_test $testname.0 {
+ DROP TABLE IF EXISTS x8;
+ CREATE VIRTUAL TABLE x8 USING fts5(i);
+ INSERT INTO x8(x8, rank) VALUES('pgsz', 32);
+
+ WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<$::nRowPerSeg)
+ INSERT INTO x8 SELECT repeat('x y ', i % 16) FROM ii;
+
+ WITH ii(i) AS (SELECT 1 UNION ALL SELECT i+1 FROM ii WHERE i<$::nRowPerSeg)
+ INSERT INTO x8 SELECT repeat('x y ', i % 16) FROM ii;
+
+ INSERT INTO x8(x8, rank) VALUES('automerge', 2);
+ }
+
+ for {set tn 1} {[lindex [fts5_level_segs x8] 0]>0} {incr tn} {
+ do_execsql_test $testname.$tn {
+ INSERT INTO x8(x8, rank) VALUES('merge', 1);
+ INSERT INTO x8(x8) VALUES('integrity-check');
+ }
+ if {$tn>5} break
+ }
+
+ do_test $testname.x [list expr "$tn < 5"] 1
+}
+
+do_merge1_test 1.1 1
+do_merge1_test 1.2 2
+do_merge1_test 1.3 3
+do_merge1_test 1.4 4
+do_merge1_test 1.5 10
+do_merge1_test 1.6 20
+do_merge1_test 1.7 100
+
+#-------------------------------------------------------------------------
+#
+proc do_merge2_test {testname nRow} {
+ db func rnddoc fts5_rnddoc
+
+ do_execsql_test $testname.0 {
+ DROP TABLE IF EXISTS x8;
+ CREATE VIRTUAL TABLE x8 USING fts5(i);
+ INSERT INTO x8(x8, rank) VALUES('pgsz', 32);
+ }
+
+ set ::nRow $nRow
+ do_test $testname.1 {
+ for {set i 0} {$i < $::nRow} {incr i} {
+ execsql { INSERT INTO x8 VALUES( rnddoc(($i%16) + 5) ) }
+ while {[not_merged x8]} {
+ execsql {
+ INSERT INTO x8(x8, rank) VALUES('automerge', 2);
+ INSERT INTO x8(x8, rank) VALUES('merge', 1);
+ INSERT INTO x8(x8, rank) VALUES('automerge', 16);
+ INSERT INTO x8(x8) VALUES('integrity-check');
+ }
+ }
+ }
+ } {}
+}
+proc not_merged {tbl} {
+ set segs [fts5_level_segs $tbl]
+ foreach s $segs { if {$s>1} { return 1 } }
+ return 0
+}
+
+do_merge2_test 2.1 5
+do_merge2_test 2.2 10
+do_merge2_test 2.3 20
+
+#-------------------------------------------------------------------------
+# Test that an auto-merge will complete any merge that has already been
+# started, even if the number of input segments is less than the current
+# value of the 'automerge' configuration parameter.
+#
+db func rnddoc fts5_rnddoc
+
+do_execsql_test 3.1 {
+ DROP TABLE IF EXISTS x8;
+ CREATE VIRTUAL TABLE x8 USING fts5(i);
+ INSERT INTO x8(x8, rank) VALUES('pgsz', 32);
+ INSERT INTO x8 VALUES(rnddoc(100));
+ INSERT INTO x8 VALUES(rnddoc(100));
+}
+do_test 3.2 {
+ execsql {
+ INSERT INTO x8(x8, rank) VALUES('automerge', 4);
+ INSERT INTO x8(x8, rank) VALUES('merge', 1);
+ }
+ fts5_level_segs x8
+} {2}
+
+do_test 3.3 {
+ execsql {
+ INSERT INTO x8(x8, rank) VALUES('automerge', 2);
+ INSERT INTO x8(x8, rank) VALUES('merge', 1);
+ }
+ fts5_level_segs x8
+} {2 1}
+
+do_test 3.4 {
+ execsql { INSERT INTO x8(x8, rank) VALUES('automerge', 4) }
+ while {[not_merged x8]} {
+ execsql { INSERT INTO x8(x8, rank) VALUES('merge', 1) }
+ }
+ fts5_level_segs x8
+} {0 1}
+
+finish_test
+
-C Improve\scoverage\sof\sfts5_index.c\sslightly.
-D 2015-04-25T20:29:46.707
+C Further\stests\sfor\sfts5.
+D 2015-04-27T11:31:56.573
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 1eb8ca073be5222c43e4eee5408764c2cbb4200b
+F ext/fts5/fts5.c 3383b8a44766c68bda812b68ce74684c6b87787f
F ext/fts5/fts5.h 24a2cc35b5e76eec57b37ba48c12d9d2cb522b3a
-F ext/fts5/fts5Int.h 803fd2fc03e3799a38ebb404f2f1309ded5d3e8b
+F ext/fts5/fts5Int.h 7dc902e195e00c5820b85d71c2476500c2cf9027
F ext/fts5/fts5_aux.c fcea18b1a2a3f95a498b52aba2983557d7678a22
F ext/fts5/fts5_buffer.c 3ba56cc6824c9f7b1e0695159e0a9c636f6b4a23
F ext/fts5/fts5_config.c 43fcf838d3a3390d1245e3d5e651fa5cc1df575b
F ext/fts5/fts5_expr.c 05da381ab26031243266069302c6eb4094b2c5dd
F ext/fts5/fts5_hash.c 3cb5a3d04dd2030eb0ac8d544711dfd37c0e6529
-F ext/fts5/fts5_index.c 699b716f1b84ef78da9ccee25a8b6fe020cff32a
-F ext/fts5/fts5_storage.c b3a4cbbcd197fe587789398e51a631f92fc9196c
+F ext/fts5/fts5_index.c 5f969f5b6f5e022c9c62973dd6e4e82e1d5f3f30
+F ext/fts5/fts5_storage.c 87f85986a6d07391f4e0ddfa9799dbecc40fa165
F ext/fts5/fts5_tcl.c 10bf0eb678d34c1bfdcfaf653d2e6dd92afa8b38
F ext/fts5/fts5_tokenize.c c07f2c2f749282c1dbbf46bde1f6d7095c740b8b
F ext/fts5/fts5_unicode2.c f74f53316377068812a1fa5a37819e6b8124631d
F ext/fts5/test/fts5ea.test 04695560a444fcc00c3c4f27783bdcfbf71f030c
F ext/fts5/test/fts5eb.test 728a1f23f263548f5c29b29dfb851b5f2dbe723e
F ext/fts5/test/fts5fault1.test ed71717a479bef32d05f02d9c48691011d160d4d
-F ext/fts5/test/fts5fault2.test 0476720b3fcbb3f30c26da4cc8bda4f020d3e408
+F ext/fts5/test/fts5fault2.test 37c325cb3e6e27c74c122bb49673b4ae0862df33
+F ext/fts5/test/fts5fault3.test f8935b92976ae645d43205562fdbb0c8511dd049
F ext/fts5/test/fts5full.test 0924bdca5416a242103239ace79c6f5aa34bab8d
+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 tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P e748651c940eae2389fe826cf5c25f1166a5e611
-R 0f1e32978d2225a38a865fc7c17144a3
+P e5aaa01306597ffd2475dcb83ae889393f68d315
+R 328723993a482062ac5cfa3f373fa969
U dan
-Z 504cb23846c7efd7bba629c51f43c91a
+Z dd82cb2122e4379a606aef38aa888497
-e5aaa01306597ffd2475dcb83ae889393f68d315
\ No newline at end of file
+ffeb3ef3cfec3681b72bb28cfa612aa15e07887d
\ No newline at end of file