/** * Set up a raw page so that it looks like a database page holding no entries. * * @param sqlJetBtree * @param flags * @throws SqlJetException */ void zeroPage(int flags) throws SqlJetException { ISqlJetMemoryPointer data = aData; byte hdr = hdrOffset; int first; assert (pDbPage.getPageNumber() == pgno); assert (pDbPage.getExtra() == this); assert (pDbPage.getData().getBuffer() == data.getBuffer()); assert (pBt.mutex.held()); SqlJetUtility.putUnsignedByte(data, hdr, (short) flags); first = hdr + 8 + 4 * ((flags & SqlJetMemPage.PTF_LEAF) == 0 ? 1 : 0); // SqlJetUtility.memset(data, hdr + 1, (byte) 0, 4); SqlJetUtility.put4byte(data, hdr + 1, 0); // SqlJetUtility.putUnsignedByte(data, hdr + 7, (short) 0); SqlJetUtility.put2byte(data, hdr + 5, pBt.usableSize); nFree = pBt.usableSize - first; decodeFlags(flags); hdrOffset = hdr; cellOffset = first; nOverflow = 0; assert (pBt.pageSize >= 512 && pBt.pageSize <= 32768); maskPage = pBt.pageSize - 1; nCell = 0; isInit = true; }
/** * Somewhere on pPage, which is guarenteed to be a btree page, not an overflow page, is a pointer * to page iFrom. Modify this pointer so that it points to iTo. Parameter eType describes the type * of pointer to be modified, as follows: * * <p>PTRMAP_BTREE: pPage is a btree-page. The pointer points at a child page of pPage. * * <p>PTRMAP_OVERFLOW1: pPage is a btree-page. The pointer points at an overflow page pointed to * by one of the cells on pPage. * * <p>PTRMAP_OVERFLOW2: pPage is an overflow-page. The pointer points at the next overflow page in * the list. * * @throws SqlJetExceptionRemove */ public void modifyPagePointer(int iFrom, int iTo, short s) throws SqlJetException { assert (pBt.mutex.held()); if (s == SqlJetBtreeShared.PTRMAP_OVERFLOW2) { /* The pointer is always the first 4 bytes of the page in this case. */ if (get4byte(aData) != iFrom) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } put4byte(aData, iTo); } else { boolean isInitOrig = this.isInit; int i; int nCell; initPage(); nCell = this.nCell; for (i = 0; i < nCell; i++) { ISqlJetMemoryPointer pCell = findCell(i); if (s == SqlJetBtreeShared.PTRMAP_OVERFLOW1) { SqlJetBtreeCellInfo info; info = parseCellPtr(pCell); if (info.iOverflow > 0) { if (iFrom == get4byte(pCell, info.iOverflow)) { put4byte(pCell, info.iOverflow, iTo); break; } } } else { if (get4byte(pCell) == iFrom) { put4byte(pCell, iTo); break; } } } if (i == nCell) { if (s != SqlJetBtreeShared.PTRMAP_BTREE || get4byte(aData, hdrOffset + 8) != iFrom) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } put4byte(aData, hdrOffset + 8, iTo); } this.isInit = isInitOrig; } }
/** * Insert a new cell on pPage at cell index "i". pCell points to the content of the cell. * * <p>If the cell content will fit on the page, then put it there. If it will not fit, then make a * copy of the cell content into pTemp if pTemp is not null. Regardless of pTemp, allocate a new * entry in pPage->aOvfl[] and make it point to the cell content (either in pTemp or the original * pCell) and also record its index. Allocating a new entry in pPage->aCell[] implies that * pPage->nOverflow is incremented. * * <p>If nSkip is non-zero, then do not copy the first nSkip bytes of the cell. The caller will * overwrite them after this function returns. If nSkip is non-zero, then pCell may not point to * an invalid memory location (but pCell+nSkip is always valid). * * @param i New cell becomes the i-th cell of the page * @param pCell Content of the new cell * @param sz Bytes of content in pCell * @param pTemp Temp storage space for pCell, if needed * @param nSkip Do not write the first nSkip bytes of the cell * @throws SqlJetException */ public void insertCell( int i, ISqlJetMemoryPointer pCell, int sz, ISqlJetMemoryPointer pTemp, int iChild) throws SqlJetException { int nSkip = (iChild > 0 ? 4 : 0); final SqlJetMemPage pPage = this; int idx; /* Where to write new cell content in data[] */ int j; /* Loop counter */ int top; /* First byte of content for any cell in data[] */ int end; /* First byte past the last cell pointer in data[] */ int ins; /* Index in data[] where new cell pointer is inserted */ int hdr; /* Offset into data[] of the page header */ int cellOffset; /* Address of first cell pointer in data[] */ ISqlJetMemoryPointer data; /* The content of the whole page */ assert (i >= 0 && i <= pPage.nCell + pPage.nOverflow); assert (pPage.nCell <= pPage.pBt.MX_CELL() && pPage.pBt.MX_CELL() <= 5460); assert (pPage.nOverflow <= pPage.aOvfl.length); assert (sz == pPage.cellSizePtr(pCell) || (sz == 8 && iChild > 0)); assert (pPage.pBt.mutex.held()); if (pPage.nOverflow != 0 || sz + 2 > pPage.nFree) { if (pTemp != null) { memcpy(pTemp, nSkip, pCell, nSkip, sz - nSkip); pCell = pTemp; } if (iChild > 0) { put4byte(pCell, iChild); } j = pPage.nOverflow++; // assert( j<(int)(sizeof(pPage.aOvfl)/sizeof(pPage.aOvfl[0])) ); pPage.aOvfl[j].pCell = pCell; pPage.aOvfl[j].idx = i; } else { pPage.pDbPage.write(); assert (pPage.pDbPage.isWriteable()); data = pPage.aData; hdr = pPage.hdrOffset; top = get2byte(data, hdr + 5); cellOffset = pPage.cellOffset; end = cellOffset + 2 * pPage.nCell + 2; ins = cellOffset + 2 * i; if (end > top - sz) { pPage.defragmentPage(); top = get2byte(data, hdr + 5); assert (end + sz <= top); } idx = pPage.allocateSpace(sz); assert (idx > 0); assert (end <= get2byte(data, hdr + 5)); if (idx + sz > pPage.pBt.usableSize) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } pPage.nCell++; pPage.nFree -= (2 + sz); memcpy(data, idx + nSkip, pCell, nSkip, sz - nSkip); if (iChild > 0) { put4byte(data, idx, iChild); } for (j = end - 2; j > ins; j -= 2) { SqlJetUtility.putUnsignedByte(data, j, SqlJetUtility.getUnsignedByte(data, j - 2)); SqlJetUtility.putUnsignedByte(data, j + 1, SqlJetUtility.getUnsignedByte(data, j - 1)); } put2byte(data, ins, idx); put2byte(data, hdr + 3, pPage.nCell); if (pPage.pBt.autoVacuum) { /* * The cell may contain a pointer to an overflow page. If so, * write* the entry for the overflow page into the pointer map. */ SqlJetBtreeCellInfo info = pPage.parseCellPtr(pCell); assert ((info.nData + (pPage.intKey ? 0 : info.nKey)) == info.nPayload); if ((info.nData + (pPage.intKey ? 0 : info.nKey)) > info.nLocal) { int pgnoOvfl = get4byte(pCell, info.iOverflow); pPage.pBt.ptrmapPut(pgnoOvfl, SqlJetBtreeShared.PTRMAP_OVERFLOW1, pPage.pgno); } } } }
/** Add a page of the database file to the freelist. unref() is NOT called for pPage. */ public void freePage() throws SqlJetException { SqlJetMemPage pPage1 = pBt.pPage1; int n, k; /* Prepare the page for freeing */ assert (pBt.mutex.held()); assert (this.pgno > 1); this.isInit = false; /* Increment the free page count on pPage1 */ pPage1.pDbPage.write(); n = get4byte(pPage1.aData, 36); put4byte(pPage1.aData, 36, n + 1); if (ISqlJetConfig.SECURE_DELETE) { /* * If the SQLITE_SECURE_DELETE compile-time option is enabled, then * always fully overwrite deleted information with zeros. */ pDbPage.write(); memset(aData, (byte) 0, pBt.pageSize); } /* * If the database supports auto-vacuum, write an entry in the * pointer-map to indicate that the page is free. */ if (ISAUTOVACUUM()) { pBt.ptrmapPut(pgno, SqlJetBtreeShared.PTRMAP_FREEPAGE, 0); } if (n == 0) { /* This is the first free page */ pDbPage.write(); memset(aData, (byte) 0, 8); put4byte(pPage1.aData, 32, pgno); TRACE("FREE-PAGE: %d first\n", this.pgno); } else { /* * Other free pages already exist. Retrive the first trunk page* of * the freelist and find out how many leaves it has. */ SqlJetMemPage pTrunk; pTrunk = pBt.getPage(get4byte(pPage1.aData, 32), false); k = get4byte(pTrunk.aData, 4); if (k >= pBt.usableSize / 4 - 8) { /* * The trunk is full. Turn the page being freed into a new* * trunk page with no leaves.** Note that the trunk page is not * really full until it contains* usableSize/4 - 2 entries, not * usableSize/4 - 8 entries as we have* coded. But due to a * coding error in versions of SQLite prior to* 3.6.0, databases * with freelist trunk pages holding more than* usableSize/4 - 8 * entries will be reported as corrupt. In order* to maintain * backwards compatibility with older versions of SQLite,* we * will contain to restrict the number of entries to * usableSize/4 - 8* for now. At some point in the future (once * everyone has upgraded* to 3.6.0 or later) we should consider * fixing the conditional above* to read "usableSize/4-2" * instead of "usableSize/4-8". */ pDbPage.write(); put4byte(aData, pTrunk.pgno); put4byte(aData, 4, 0); put4byte(pPage1.aData, 32, pgno); TRACE("FREE-PAGE: %d new trunk page replacing %d\n", this.pgno, pTrunk.pgno); } else if (k < 0) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } else { /* Add the newly freed page as a leaf on the current trunk */ pTrunk.pDbPage.write(); put4byte(pTrunk.aData, 4, k + 1); put4byte(pTrunk.aData, 8 + k * 4, pgno); if (ISqlJetConfig.SECURE_DELETE) { pDbPage.dontWrite(); } TRACE("FREE-PAGE: %d leaf on trunk page %d\n", this.pgno, pTrunk.pgno); } releasePage(pTrunk); } }
/** * Create the byte sequence used to represent a cell on page pPage and write that byte sequence * into pCell[]. Overflow pages are allocated and filled in as necessary. The calling procedure is * responsible for making sure sufficient space has been allocated for pCell[]. * * <p>Note that pCell does not necessary need to point to the pPage->aData area. pCell might point * to some temporary storage. The cell will be constructed in this temporary area then copied into * pPage->aData later. * * @param pCell Complete text of the cell * @param pKey The key * @param nKey The key * @param pData The data * @param nData The data * @param nZero Extra zero bytes to append to pData * @return cell size * @throws SqlJetException */ public int fillInCell( ISqlJetMemoryPointer pCell, ISqlJetMemoryPointer pKey, long nKey, ISqlJetMemoryPointer pData, int nData, int nZero) throws SqlJetException { final SqlJetMemPage pPage = this; int pnSize = 0; int nPayload; ISqlJetMemoryPointer pSrc; int nSrc, n; int spaceLeft; SqlJetMemPage pOvfl = null; SqlJetMemPage pToRelease = null; ISqlJetMemoryPointer pPrior; ISqlJetMemoryPointer pPayload; SqlJetBtreeShared pBt = pPage.pBt; int[] pgnoOvfl = {0}; int nHeader; SqlJetBtreeCellInfo info; assert (pPage.pBt.mutex.held()); /* * pPage is not necessarily writeable since pCell might be auxiliary* * buffer space that is separate from the pPage buffer area */ assert (pCell.getBuffer() != pPage.aData.getBuffer() || pPage.pDbPage.isWriteable()); /* Fill in the header. */ nHeader = 0; if (!pPage.leaf) { nHeader += 4; } if (pPage.hasData) { nHeader += putVarint(pointer(pCell, nHeader), nData + nZero); } else { nData = nZero = 0; } nHeader += putVarint(pointer(pCell, nHeader), nKey); info = pPage.parseCellPtr(pCell); assert (info.nHeader == nHeader); assert (info.nKey == nKey); assert (info.nData == nData + nZero); /* Fill in the payload */ nPayload = nData + nZero; if (pPage.intKey) { pSrc = pData; nSrc = nData; nData = 0; } else { /* TBD: Perhaps raise SQLITE_CORRUPT if nKey is larger than 31 bits? */ nPayload += (int) nKey; pSrc = pKey; nSrc = (int) nKey; } pnSize = info.nSize; spaceLeft = info.nLocal; pPayload = pointer(pCell, nHeader); pPrior = pointer(pCell, info.iOverflow); while (nPayload > 0) { if (spaceLeft == 0) { int pgnoPtrmap = pgnoOvfl[0]; /* * Overflow page pointer-map entry * page */ if (pBt.autoVacuum) { do { pgnoOvfl[0]++; } while (pBt.PTRMAP_ISPAGE(pgnoOvfl[0]) || pgnoOvfl[0] == pBt.PENDING_BYTE_PAGE()); } try { pOvfl = pBt.allocatePage(pgnoOvfl, pgnoOvfl[0], false); /* * If the database supports auto-vacuum, and the second or * subsequent* overflow page is being allocated, add an * entry to the pointer-map* for that page now.** If this is * the first overflow page, then write a partial entry* to * the pointer-map. If we write nothing to this pointer-map * slot,* then the optimistic overflow chain processing in * clearCell()* may misinterpret the uninitialised values * and delete the* wrong pages from the database. */ if (pBt.autoVacuum) { byte eType = (pgnoPtrmap != 0 ? SqlJetBtreeShared.PTRMAP_OVERFLOW2 : SqlJetBtreeShared.PTRMAP_OVERFLOW1); try { pBt.ptrmapPut(pgnoOvfl[0], eType, pgnoPtrmap); } catch (SqlJetException e) { releasePage(pOvfl); } } } catch (SqlJetException e) { releasePage(pToRelease); throw e; } /* * If pToRelease is not zero than pPrior points into the data * area* of pToRelease. Make sure pToRelease is still writeable. */ assert (pToRelease == null || pToRelease.pDbPage.isWriteable()); /* * If pPrior is part of the data area of pPage, then make sure * pPage* is still writeable */ assert (pPrior.getBuffer() != pPage.aData.getBuffer() || pPage.pDbPage.isWriteable()); put4byte(pPrior, pgnoOvfl[0]); releasePage(pToRelease); pToRelease = pOvfl; pPrior = pOvfl.aData; put4byte(pPrior, 0); pPayload = pointer(pOvfl.aData, 4); spaceLeft = pBt.usableSize - 4; } n = nPayload; if (n > spaceLeft) n = spaceLeft; /* * If pToRelease is not zero than pPayload points into the data area * * of pToRelease. Make sure pToRelease is still writeable. */ assert (pToRelease == null || pToRelease.pDbPage.isWriteable()); /* * If pPayload is part of the data area of pPage, then make sure * pPage* is still writeable */ assert (pPayload.getBuffer() != pPage.aData.getBuffer() || pPage.pDbPage.isWriteable()); if (nSrc > 0) { if (n > nSrc) n = nSrc; assert (pSrc != null); memcpy(pPayload, pSrc, n); } else { memset(pPayload, (byte) 0, n); } nPayload -= n; movePtr(pPayload, n); pSrc = pointer(pSrc, n); nSrc -= n; spaceLeft -= n; if (nSrc == 0) { nSrc = nData; pSrc = pData; } } releasePage(pToRelease); return pnSize; }