Skip to content

Commit bba4f2a

Browse files
authored
Merge pull request #2293 from adamretter/hotfix/journal-2gb-limit
Remove the 2GB log file limit for the Journal
2 parents b257464 + 14ea04a commit bba4f2a

File tree

19 files changed

+287
-123
lines changed

19 files changed

+287
-123
lines changed

extensions/indexes/ngram/src/org/exist/indexing/ngram/NGramIndex.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
*/
4343
public class NGramIndex extends AbstractIndex implements RawBackupSupport {
4444

45-
public static final short FILE_FORMAT_VERSION_ID = 13;
45+
public static final short FILE_FORMAT_VERSION_ID = 14;
4646

4747
public final static String ID = NGramIndex.class.getName();
4848

extensions/indexes/sort/src/org/exist/indexing/sort/SortIndex.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public class SortIndex extends AbstractIndex implements RawBackupSupport {
3535

3636
public static final String ID = SortIndex.class.getName();
3737
public static final String FILE_NAME = "sort.dbx";
38-
public final static short FILE_FORMAT_VERSION_ID = 2;
38+
public final static short FILE_FORMAT_VERSION_ID = 3;
3939
public static final byte SORT_INDEX_ID = 0x10;
4040
protected static final Logger LOG = LogManager.getLogger(SortIndex.class);
4141
protected BTreeStore btree;

src/org/exist/storage/NativeValueIndex.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public class NativeValueIndex implements ContentLoadingObserver {
119119
private final static Logger LOG = LogManager.getLogger(NativeValueIndex.class);
120120

121121
public static final String FILE_NAME = "values.dbx";
122-
public static final short FILE_FORMAT_VERSION_ID = 13;
122+
public static final short FILE_FORMAT_VERSION_ID = 14;
123123
public static final String FILE_KEY_IN_CONFIG = "db-connection.values";
124124

125125
private static final double DEFAULT_VALUE_CACHE_GROWTH = 1.25;

src/org/exist/storage/btree/BTree.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -748,7 +748,7 @@ private void writeToLog(final Loggable loggable, final BTreeNode node) {
748748
}
749749

750750
protected boolean requiresRedo(final Loggable loggable, final Page page) {
751-
return loggable.getLsn() > page.getPageHeader().getLsn();
751+
return loggable.getLsn().compareTo(page.getPageHeader().getLsn()) > 0;
752752
}
753753

754754
protected void redoCreateBTNode(final CreateBTNodeLoggable loggable) throws LogException {
@@ -760,7 +760,7 @@ protected void redoCreateBTNode(final CreateBTNodeLoggable loggable) throws LogE
760760
page.read();
761761
if ((page.getPageHeader().getStatus() == BRANCH ||
762762
page.getPageHeader().getStatus() == LEAF) &&
763-
page.getPageHeader().getLsn() != Lsn.LSN_INVALID &&
763+
(!page.getPageHeader().getLsn().equals(Lsn.LSN_INVALID)) &&
764764
!requiresRedo(loggable, page)) {
765765
// node already found on disk: read it
766766
node = new BTreeNode(page, false);
@@ -803,7 +803,7 @@ protected void undoInsertValue(final InsertValueLoggable loggable) throws LogExc
803803

804804
protected void redoUpdateValue(final UpdateValueLoggable loggable) throws LogException {
805805
final BTreeNode node = getBTreeNode(loggable.pageNum);
806-
if (node.page.getPageHeader().getLsn() != Page.NO_PAGE && requiresRedo(loggable, node.page)) {
806+
if (!node.page.getPageHeader().getLsn().equals(Lsn.LSN_INVALID) && requiresRedo(loggable, node.page)) {
807807
if (loggable.idx > node.ptrs.length) {
808808
LOG.warn(node.page.getPageInfo() +
809809
"; loggable.idx = " + loggable.idx + "; node.ptrs.length = " + node.ptrs.length);
@@ -833,7 +833,7 @@ protected void undoUpdateValue(final UpdateValueLoggable loggable) throws LogExc
833833

834834
protected void redoRemoveValue(final RemoveValueLoggable loggable) throws LogException {
835835
final BTreeNode node = getBTreeNode(loggable.pageNum);
836-
if (node.page.getPageHeader().getLsn() != Page.NO_PAGE && requiresRedo(loggable, node.page)) {
836+
if (!node.page.getPageHeader().getLsn().equals(Lsn.LSN_INVALID) && requiresRedo(loggable, node.page)) {
837837
node.removeKey(loggable.idx);
838838
node.removePointer(loggable.idx);
839839
node.recalculateDataLen();

src/org/exist/storage/btree/Paged.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,15 +1014,15 @@ public static abstract class PageHeader {
10141014
public static final int LENGTH_PAGE_STATUS = 1; //sizeof byte
10151015
public static final int LENGTH_PAGE_DATA_LENGTH = 4; //sizeof int
10161016
public static final int LENGTH_PAGE_NEXT_PAGE = 8; //sizeof long
1017-
public static final int LENGTH_PAGE_LSN = 8; //sizeof long
1017+
public static final int LENGTH_PAGE_LSN = Lsn.RAW_LENGTH;
10181018

10191019
private int dataLen = 0;
10201020
private boolean dirty = false;
10211021
private long nextPage = Page.NO_PAGE;
10221022

10231023
private byte status = UNUSED;
10241024

1025-
private long lsn = Lsn.LSN_INVALID;
1025+
private Lsn lsn = Lsn.LSN_INVALID;
10261026

10271027
public PageHeader() {
10281028
}
@@ -1081,11 +1081,11 @@ public final boolean isDirty() {
10811081
*
10821082
* @return log sequence number of the last operation that modified this page.
10831083
*/
1084-
public final long getLsn() {
1084+
public final Lsn getLsn() {
10851085
return lsn;
10861086
}
10871087

1088-
public final void setLsn(final long lsn) {
1088+
public final void setLsn(final Lsn lsn) {
10891089
this.lsn = lsn;
10901090
}
10911091

@@ -1096,7 +1096,7 @@ public int read(final byte[] data, int offset) throws IOException {
10961096
offset += LENGTH_PAGE_DATA_LENGTH;
10971097
nextPage = ByteConversion.byteToLong(data, offset);
10981098
offset += LENGTH_PAGE_NEXT_PAGE;
1099-
lsn = ByteConversion.byteToLong(data, offset);
1099+
lsn = Lsn.read(data, offset);
11001100
offset += LENGTH_PAGE_LSN;
11011101
return offset;
11021102
}
@@ -1108,7 +1108,7 @@ public int write(final byte[] data, int offset) throws IOException {
11081108
offset += LENGTH_PAGE_DATA_LENGTH;
11091109
ByteConversion.longToByte(nextPage, data, offset);
11101110
offset += LENGTH_PAGE_NEXT_PAGE;
1111-
ByteConversion.longToByte(lsn, data, offset);
1111+
lsn.write(data, offset);
11121112
offset += LENGTH_PAGE_LSN;
11131113
dirty = false;
11141114
return offset;

src/org/exist/storage/dom/DOMFile.java

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ public class DOMFile extends BTree implements Lockable {
162162
LogEntryTypes.addEntryType(LOG_UPDATE_LINK, UpdateLinkLoggable::new);
163163
}
164164

165-
public final static short FILE_FORMAT_VERSION_ID = 9;
165+
public final static short FILE_FORMAT_VERSION_ID = 10;
166166

167167
private final LockManager lockManager;
168168

@@ -2176,13 +2176,13 @@ public synchronized final void setOwnerObject(final Object ownerObject) {
21762176
*/
21772177

21782178
private boolean requiresRedo(final Loggable loggable, final DOMPage page) {
2179-
return loggable.getLsn() > page.getPageHeader().getLsn();
2179+
return loggable.getLsn().compareTo(page.getPageHeader().getLsn()) > 0;
21802180
}
21812181

21822182
protected void redoCreatePage(final CreatePageLoggable loggable) {
21832183
final DOMPage newPage = getDOMPage(loggable.newPage);
21842184
final DOMFilePageHeader newPageHeader = newPage.getPageHeader();
2185-
if (newPageHeader.getLsn() == Lsn.LSN_INVALID || requiresRedo(loggable, newPage)) {
2185+
if (newPageHeader.getLsn().equals(Lsn.LSN_INVALID) || requiresRedo(loggable, newPage)) {
21862186
try {
21872187
dropFreePageList();
21882188
newPageHeader.setStatus(RECORD);
@@ -2238,7 +2238,7 @@ protected void undoCreatePage(final CreatePageLoggable loggable) {
22382238
protected void redoAddValue(final AddValueLoggable loggable) {
22392239
final DOMPage page = getDOMPage(loggable.pageNum);
22402240
final DOMFilePageHeader pageHeader = page.getPageHeader();
2241-
if (pageHeader.getLsn() != Lsn.LSN_INVALID && requiresRedo(loggable, page)) {
2241+
if ((!pageHeader.getLsn().equals(Lsn.LSN_INVALID)) && requiresRedo(loggable, page)) {
22422242
try {
22432243
ByteConversion.shortToByte(loggable.tid, page.data, page.len);
22442244
page.len += LENGTH_TID;
@@ -2270,7 +2270,7 @@ protected void undoAddValue(final AddValueLoggable loggable) {
22702270
final DOMFilePageHeader pageHeader = page.getPageHeader();
22712271

22722272
// is there anything to undo?
2273-
if (pageHeader.getLsn() == Lsn.LSN_INVALID || pageHeader.getStatus() == UNUSED) {
2273+
if (pageHeader.getLsn().equals(Lsn.LSN_INVALID) || pageHeader.getStatus() == UNUSED) {
22742274
LOG.warn("Nothing to undo, but received: AddValueLoggable(txnId=" + loggable.getTransactionId()
22752275
+ ", lsn=" + loggable.getLsn() + ", pageNum=" + loggable.pageNum
22762276
+ ", isOverflow=" + loggable.isOverflow + ")");
@@ -2302,7 +2302,7 @@ protected void undoAddValue(final AddValueLoggable loggable) {
23022302
protected void redoUpdateValue(final UpdateValueLoggable loggable) {
23032303
final DOMPage page = getDOMPage(loggable.pageNum);
23042304
final DOMFilePageHeader ph = page.getPageHeader();
2305-
if (ph.getLsn() != Lsn.LSN_INVALID && requiresRedo(loggable, page)) {
2305+
if ((!ph.getLsn().equals(Lsn.LSN_INVALID)) && requiresRedo(loggable, page)) {
23062306
final RecordPos rec = page.findRecord(ItemId.getId(loggable.tid));
23072307
SanityCheck.THROW_ASSERT(rec != null,
23082308
"tid " + ItemId.getId(loggable.tid) +
@@ -2342,7 +2342,7 @@ protected void undoUpdateValue(final UpdateValueLoggable loggable) {
23422342
protected void redoRemoveValue(final RemoveValueLoggable loggable) {
23432343
final DOMPage page = getDOMPage(loggable.pageNum);
23442344
final DOMFilePageHeader pageHeader = page.getPageHeader();
2345-
if (pageHeader.getLsn() != Lsn.LSN_INVALID && requiresRedo(loggable, page)) {
2345+
if ((!pageHeader.getLsn().equals(Lsn.LSN_INVALID)) && requiresRedo(loggable, page)) {
23462346
final RecordPos pos = page.findRecord(ItemId.getId(loggable.tid));
23472347
SanityCheck.ASSERT(pos != null,
23482348
"Record not found: " + ItemId.getId(loggable.tid) + ": " +
@@ -2445,7 +2445,7 @@ protected void undoRemoveValue(final RemoveValueLoggable loggable) {
24452445
protected void redoRemoveEmptyPage(final RemoveEmptyPageLoggable loggable) {
24462446
final DOMPage page = getDOMPage(loggable.pageNum);
24472447
final DOMFilePageHeader pageHeader = page.getPageHeader();
2448-
if (pageHeader.getLsn() != Lsn.LSN_INVALID && requiresRedo(loggable, page)) {
2448+
if ((!pageHeader.getLsn().equals(Lsn.LSN_INVALID)) && requiresRedo(loggable, page)) {
24492449
removePage(page);
24502450
}
24512451
}
@@ -2487,7 +2487,7 @@ protected void undoRemoveEmptyPage(final RemoveEmptyPageLoggable loggable) {
24872487
protected void redoRemovePage(final RemovePageLoggable loggable) {
24882488
final DOMPage page = getDOMPage(loggable.pageNum);
24892489
final DOMFilePageHeader pageHeader = page.getPageHeader();
2490-
if (pageHeader.getLsn() != Lsn.LSN_INVALID && requiresRedo(loggable, page)) {
2490+
if ((!pageHeader.getLsn().equals(Lsn.LSN_INVALID)) && requiresRedo(loggable, page)) {
24912491
try {
24922492
pageHeader.setNextDataPage(Page.NO_PAGE);
24932493
pageHeader.setPrevDataPage(Page.NO_PAGE);
@@ -2531,7 +2531,7 @@ protected void redoWriteOverflow(final WriteOverflowPageLoggable loggable) {
25312531
try {
25322532
final Page page = getPage(loggable.pageNum);
25332533
final PageHeader pageHeader = page.getPageHeader();
2534-
if (pageHeader.getLsn() != Lsn.LSN_INVALID && requiresRedo(loggable, page)) {
2534+
if ((!pageHeader.getLsn().equals(Lsn.LSN_INVALID)) && requiresRedo(loggable, page)) {
25352535

25362536
dropFreePageList();
25372537
pageHeader.setStatus(RECORD);
@@ -2566,7 +2566,7 @@ protected void redoRemoveOverflow(final RemoveOverflowLoggable loggable) {
25662566
final Page page = getPage(loggable.pageNum);
25672567
page.read();
25682568
final PageHeader pageHeader = page.getPageHeader();
2569-
if (pageHeader.getLsn() != Lsn.LSN_INVALID && requiresRedo(loggable, page)) {
2569+
if ((!pageHeader.getLsn().equals(Lsn.LSN_INVALID)) && requiresRedo(loggable, page)) {
25702570
unlinkPages(page);
25712571
}
25722572
} catch (final IOException e) {
@@ -2597,7 +2597,7 @@ protected void undoRemoveOverflow(final RemoveOverflowLoggable loggable) {
25972597
protected void redoInsertValue(final InsertValueLoggable loggable) {
25982598
final DOMPage page = getDOMPage(loggable.pageNum);
25992599
final DOMFilePageHeader pageHeader = page.getPageHeader();
2600-
if (pageHeader.getLsn() != Lsn.LSN_INVALID && requiresRedo(loggable, page)) {
2600+
if ((!pageHeader.getLsn().equals(Lsn.LSN_INVALID)) && requiresRedo(loggable, page)) {
26012601
final int dlen = pageHeader.getDataLength();
26022602
int offset = loggable.offset;
26032603
// insert in the middle of the page?
@@ -2684,7 +2684,7 @@ protected void undoInsertValue(final InsertValueLoggable loggable) {
26842684
protected void redoSplitPage(final SplitPageLoggable loggable) {
26852685
final DOMPage page = getDOMPage(loggable.pageNum);
26862686
final DOMFilePageHeader pageHeader = page.getPageHeader();
2687-
if (pageHeader.getLsn() != Lsn.LSN_INVALID && requiresRedo(loggable, page)) {
2687+
if ((!pageHeader.getLsn().equals(Lsn.LSN_INVALID)) && requiresRedo(loggable, page)) {
26882688
final byte[] oldData = page.data;
26892689
page.data = new byte[fileHeader.getWorkSize()];
26902690
System.arraycopy(oldData, 0, page.data, 0, loggable.splitOffset);
@@ -2716,7 +2716,7 @@ protected void undoSplitPage(final SplitPageLoggable loggable) {
27162716
protected void redoAddLink(final AddLinkLoggable loggable) {
27172717
final DOMPage page = getDOMPage(loggable.pageNum);
27182718
final DOMFilePageHeader pageHeader = page.getPageHeader();
2719-
if (pageHeader.getLsn() != Lsn.LSN_INVALID && requiresRedo(loggable, page)) {
2719+
if ((!pageHeader.getLsn().equals(Lsn.LSN_INVALID)) && requiresRedo(loggable, page)) {
27202720
ByteConversion.shortToByte(ItemId.setIsLink(loggable.tid), page.data, page.len);
27212721
page.len += LENGTH_TID;
27222722
ByteConversion.longToByte(loggable.link, page.data, page.len);
@@ -2752,7 +2752,7 @@ protected void undoAddLink(final AddLinkLoggable loggable) {
27522752
protected void redoUpdateLink(final UpdateLinkLoggable loggable) {
27532753
final DOMPage page = getDOMPage(loggable.pageNum);
27542754
final DOMFilePageHeader pageHeader = page.getPageHeader();
2755-
if (pageHeader.getLsn() != Lsn.LSN_INVALID && requiresRedo(loggable, page)) {
2755+
if ((!pageHeader.getLsn().equals(Lsn.LSN_INVALID)) && requiresRedo(loggable, page)) {
27562756
ByteConversion.longToByte(loggable.link, page.data, loggable.offset);
27572757
pageHeader.setLsn(loggable.getLsn());
27582758
page.setDirty(true);
@@ -2772,7 +2772,7 @@ protected void undoUpdateLink(final UpdateLinkLoggable loggable) {
27722772
protected void redoAddMovedValue(final AddMovedValueLoggable loggable) {
27732773
final DOMPage page = getDOMPage(loggable.pageNum);
27742774
final DOMFilePageHeader pageHeader = page.getPageHeader();
2775-
if (pageHeader.getLsn() != Lsn.LSN_INVALID && requiresRedo(loggable, page)) {
2775+
if ((!pageHeader.getLsn().equals(Lsn.LSN_INVALID)) && requiresRedo(loggable, page)) {
27762776
try {
27772777
ByteConversion.shortToByte(ItemId.setIsRelocated(loggable.tid), page.data, page.len);
27782778
page.len += LENGTH_TID;
@@ -2840,7 +2840,7 @@ protected void undoAddMovedValue(final AddMovedValueLoggable loggable) {
28402840
protected void redoUpdateHeader(final UpdateHeaderLoggable loggable) {
28412841
final DOMPage page = getDOMPage(loggable.pageNum);
28422842
final DOMFilePageHeader pageHeader = page.getPageHeader();
2843-
if (pageHeader.getLsn() != Lsn.LSN_INVALID && requiresRedo(loggable, page)) {
2843+
if ((!pageHeader.getLsn().equals(Lsn.LSN_INVALID)) && requiresRedo(loggable, page)) {
28442844
if (loggable.nextPage != Page.NO_PAGE) {
28452845
pageHeader.setNextDataPage(loggable.nextPage);
28462846
}
@@ -3190,7 +3190,7 @@ public String dumpPage() {
31903190
public boolean sync(final boolean syncJournal) {
31913191
if (isDirty()) {
31923192
write();
3193-
if (isRecoveryEnabled() && syncJournal && logManager.get().lastWrittenLsn() < pageHeader.getLsn()) {
3193+
if (isRecoveryEnabled() && syncJournal && logManager.get().lastWrittenLsn().compareTo(pageHeader.getLsn()) < 0) {
31943194
logManager.get().flush(true, false);
31953195
}
31963196
return true;

src/org/exist/storage/index/BFile.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -989,11 +989,11 @@ private SinglePage getSinglePageForRedo(final Loggable loggable, final long pos)
989989
}
990990

991991
private boolean isUptodate(final Page page, final Loggable loggable) {
992-
return page.getPageHeader().getLsn() >= loggable.getLsn();
992+
return page.getPageHeader().getLsn().compareTo(loggable.getLsn()) >= 0;
993993
}
994994

995995
private boolean requiresRedo(final Loggable loggable, final DataPage page) {
996-
return loggable.getLsn() > page.getPageHeader().getLsn();
996+
return loggable.getLsn().compareTo(page.getPageHeader().getLsn()) > 0;
997997
}
998998

999999
protected void redoStoreValue(final StoreValueLoggable loggable) {
@@ -1047,7 +1047,7 @@ protected void redoRemoveValue(final RemoveValueLoggable loggable) {
10471047
}
10481048
wp = new SinglePage(page, data, true);
10491049
}
1050-
if (wp.ph.getLsn() != Page.NO_PAGE && requiresRedo(loggable, wp)) {
1050+
if (!wp.ph.getLsn().equals(Lsn.LSN_INVALID) && requiresRedo(loggable, wp)) {
10511051
removeValueHelper(loggable, loggable.tid, wp);
10521052
}
10531053
} catch (final IOException e) {
@@ -1080,7 +1080,7 @@ protected void redoRemovePage(final RemoveEmptyPageLoggable loggable) {
10801080
}
10811081
wp = new SinglePage(page, data, false);
10821082
}
1083-
if (wp.getPageHeader().getLsn() == Lsn.LSN_INVALID || requiresRedo(loggable, wp)) {
1083+
if (wp.getPageHeader().getLsn().equals(Lsn.LSN_INVALID) || requiresRedo(loggable, wp)) {
10841084
fileHeader.removeFreeSpace(fileHeader.getFreeSpace(wp.getPageNum()));
10851085
dataCache.remove(wp);
10861086
wp.delete();
@@ -1100,7 +1100,7 @@ protected void redoCreateOverflow(final OverflowCreateLoggable loggable) {
11001100
if (firstPage == null) {
11011101
final Page page = getPage(loggable.pageNum);
11021102
byte[] data = page.read();
1103-
if (page.getPageHeader().getLsn() == Lsn.LSN_INVALID || requiresRedo(loggable, page)) {
1103+
if (page.getPageHeader().getLsn().equals(Lsn.LSN_INVALID) || requiresRedo(loggable, page)) {
11041104
dropFreePageList();
11051105
final BFilePageHeader ph = (BFilePageHeader) page.getPageHeader();
11061106
ph.setStatus(MULTI_PAGE);
@@ -1115,7 +1115,7 @@ protected void redoCreateOverflow(final OverflowCreateLoggable loggable) {
11151115
firstPage = new SinglePage(page, data, false);
11161116
}
11171117
}
1118-
if (firstPage.getPageHeader().getLsn() != Page.NO_PAGE && requiresRedo(loggable, firstPage)) {
1118+
if (!firstPage.getPageHeader().getLsn().equals(Lsn.LSN_INVALID) && requiresRedo(loggable, firstPage)) {
11191119
firstPage.getPageHeader().setLsn(loggable.getLsn());
11201120
firstPage.setDirty(true);
11211121
}
@@ -1370,7 +1370,7 @@ private DataPage createPageHelper(final Loggable loggable, final long newPage, f
13701370
if (dp == null) {
13711371
final Page page = getPage(newPage);
13721372
byte[] data = page.read();
1373-
if (page.getPageHeader().getLsn() == Lsn.LSN_INVALID || (loggable != null && requiresRedo(loggable, page)) ) {
1373+
if (page.getPageHeader().getLsn().equals(Lsn.LSN_INVALID) || (loggable != null && requiresRedo(loggable, page)) ) {
13741374
if (reuseDeleted) {
13751375
reuseDeleted(page);
13761376
} else {
@@ -1387,7 +1387,7 @@ private DataPage createPageHelper(final Loggable loggable, final long newPage, f
13871387
dp = new SinglePage(page, data, true);
13881388
}
13891389
}
1390-
if (loggable != null && loggable.getLsn() > dp.getPageHeader().getLsn()) {
1390+
if (loggable != null && loggable.getLsn().compareTo(dp.getPageHeader().getLsn()) > 0) {
13911391
dp.getPageHeader().setLsn(loggable.getLsn());
13921392
}
13931393
dp.setDirty(true);
@@ -1625,7 +1625,7 @@ public boolean sync(final boolean syncJournal) {
16251625
if (isDirty()) {
16261626
try {
16271627
write();
1628-
if (isRecoveryEnabled() && syncJournal && logManager.get().lastWrittenLsn() < getPageHeader().getLsn()) {
1628+
if (isRecoveryEnabled() && syncJournal && logManager.get().lastWrittenLsn().compareTo(getPageHeader().getLsn()) < 0) {
16291629
logManager.get().flush(true, false);
16301630
}
16311631
return true;

0 commit comments

Comments
 (0)