/** * Remove the i-th cell from pPage. This routine effects pPage only. The cell content is not freed * or deallocated. It is assumed that the cell content has been copied someplace else. This * routine just removes the reference to the cell from pPage. * * <p>"sz" must be the number of bytes in the cell. * * @param idx * @param sz * @throws SqlJetException */ public void dropCell(int idx, int sz) throws SqlJetException { final SqlJetMemPage pPage = this; int pc; /* Offset to cell content of cell being deleted */ ISqlJetMemoryPointer data; /* pPage->aData */ ISqlJetMemoryPointer ptr; /* Used to move bytes around within data[] */ assert (idx >= 0 && idx < pPage.nCell); assert (sz == pPage.cellSize(idx)); assert (pPage.pBt.mutex.held()); data = pPage.aData; ptr = pointer(data, pPage.cellOffset + 2 * idx); pc = get2byte(ptr); if ((pc < pPage.hdrOffset + 6 + (pPage.leaf ? 0 : 4)) || (pc + sz > pPage.pBt.usableSize)) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } pPage.freeSpace(pc, sz); final ISqlJetMemoryPointer endPtr = pointer(data, pPage.cellOffset + 2 * pPage.nCell - 2); while (ptr.getPointer() < endPtr.getPointer()) { put2byte(ptr, get2byte(ptr, 2)); movePtr(ptr, 2); } // put2byte(endPtr, 0); pPage.nCell--; put2byte(data, pPage.hdrOffset + 3, pPage.nCell); pPage.nFree += 2; }
/** * Add a list of cells to a page. The page should be initially empty. The cells are guaranteed to * fit on the page. * * @param nCell The number of cells to add to this page * @param apCell Pointers to cell bodies * @param aSize Sizes of the cells * @throws SqlJetException */ public void assemblePage( int nCell, ISqlJetMemoryPointer[] apCell, int apCellPos, int[] aSize, int aSizePos) throws SqlJetException { final SqlJetMemPage pPage = this; int i; /* Loop counter */ int hdr = pPage.hdrOffset; /* Index of page header */ ISqlJetMemoryPointer data = pPage.aData; /* Data for the page */ int nUsable = pPage.pBt.usableSize; assert (pPage.nOverflow == 0); assert (pPage.pBt.mutex.held()); assert (nCell >= 0 && nCell <= pPage.pBt.MX_CELL() && pPage.pBt.MX_CELL() <= 10921); assert (pPage.pDbPage.isWriteable()); assert (pPage.nCell == 0); assert (get2byte(data.getMoved(hdr + 5)) == nUsable); ISqlJetMemoryPointer pCellPtr = data.getMoved(pPage.cellOffset + nCell * 2); int cellbody = nUsable; for (i = nCell - 1; i >= 0; i--) { int sz = aSize[apCellPos + i]; pCellPtr = pCellPtr.getMoved(-2); cellbody -= sz; put2byte(pCellPtr, cellbody); memcpy(data.getMoved(cellbody), apCell[apCellPos + i], sz); } put2byte(data.getMoved(hdr + 3), nCell); put2byte(data.getMoved(hdr + 5), cellbody); pPage.nFree -= (2 * nCell + pPage.pBt.usableSize - cellbody); pPage.nCell = nCell; }
/** * 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; }
/** * Defragment the page given. All Cells are moved to the end of the page and all free space is * collected into one big FreeBlk that occurs in between the header and cell pointer array and the * cell content area. * * @throws SqlJetException */ private void defragmentPage() throws SqlJetException { final SqlJetMemPage pPage = this; int i; /* Loop counter */ int pc; /* Address of a i-th cell */ int hdr; /* Offset to the page header */ int size; /* Size of a cell */ int usableSize; /* Number of usable bytes on a page */ int cellOffset; /* Offset to the cell pointer array */ int cbrk; /* Offset to the cell content area */ int nCell; /* Number of cells on the page */ ISqlJetMemoryPointer data; /* The page data */ ISqlJetMemoryPointer temp; /* Temp area for cell content */ int iCellFirst; /* First allowable cell index */ int iCellLast; /* Last possible cell index */ assert (pPage.pDbPage.isWriteable()); assert (pPage.pBt != null); assert (pPage.pBt.usableSize <= ISqlJetLimits.SQLJET_MAX_PAGE_SIZE); assert (pPage.nOverflow == 0); assert (pPage.pBt.mutex.held()); temp = pPage.pBt.pPager.getTempSpace(); data = pPage.aData; hdr = pPage.hdrOffset; cellOffset = pPage.cellOffset; nCell = pPage.nCell; assert (nCell == get2byte(data, hdr + 3)); usableSize = pPage.pBt.usableSize; cbrk = get2byte(data, hdr + 5); memcpy(temp, cbrk, data, cbrk, usableSize - cbrk); cbrk = usableSize; iCellFirst = cellOffset + 2 * nCell; iCellLast = usableSize - 4; for (i = 0; i < nCell; i++) { final ISqlJetMemoryPointer pAddr = data.getBuffer().getPointer(cellOffset + i * 2); /* The i-th cell pointer */ pc = get2byte(pAddr); if (pc < iCellFirst || pc > iCellLast) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } assert (pc >= iCellFirst && pc <= iCellLast); size = pPage.cellSizePtr(temp.getBuffer().getPointer(pc)); cbrk -= size; if (cbrk < iCellFirst || pc + size > usableSize) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } assert (cbrk + size <= usableSize && cbrk >= iCellFirst); memcpy(data, cbrk, temp, pc, size); put2byte(pAddr, cbrk); } assert (cbrk >= iCellFirst); put2byte(data, hdr + 5, cbrk); SqlJetUtility.putUnsignedByte(data, hdr + 1, (byte) 0); SqlJetUtility.putUnsignedByte(data, hdr + 2, (byte) 0); SqlJetUtility.putUnsignedByte(data, hdr + 7, (byte) 0); memset(data, iCellFirst, (byte) 0, cbrk - iCellFirst); assert (pPage.pDbPage.isWriteable()); if (cbrk - iCellFirst != pPage.nFree) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } }
/** * Allocate nByte bytes of space on a page. * * <p>Return the index into pPage->aData[] of the first byte of the new allocation. The caller * guarantees that there is enough space. This routine will never fail. * * <p>If the page contains nBytes of free space but does not contain nBytes of contiguous free * space, then this routine automatically calls defragementPage() to consolidate all free space * before allocating the new chunk. * * @param nByte * @return * @throws SqlJetException */ private int allocateSpace(int nByte) throws SqlJetException { final SqlJetMemPage pPage = this; int addr, pc, hdr; int size; int nFrag; ISqlJetMemoryPointer data; data = pPage.aData; assert (pPage.pDbPage.isWriteable()); assert (pPage.pBt != null); assert (pPage.pBt.mutex.held()); assert (nByte >= 0); /* Minimum cell size is 4 */ assert (pPage.nFree >= nByte); assert (pPage.nOverflow == 0); int usableSize = pPage.pBt.usableSize; assert (nByte < usableSize - 8); hdr = pPage.hdrOffset; nFrag = SqlJetUtility.getUnsignedByte(data, hdr + 7); assert (pPage.cellOffset == hdr + 12 - 4 * (pPage.leaf ? 1 : 0)); int gap = pPage.cellOffset + 2 * pPage.nCell; int top = get2byte(data.getMoved(hdr + 5)); if (gap > top) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } if (nFrag >= 60) { pPage.defragmentPage(); top = get2byte(data.getMoved(hdr + 5)); } else if (gap + 2 <= top) { addr = hdr + 1; while ((pc = get2byte(data, addr)) > 0) { if (pc > usableSize - 4 || pc < addr + 4) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } size = get2byte(data, pc + 2); if (size >= nByte) { int x = size - nByte; if (x < 4) { memcpy(data, addr, data, pc, 2); SqlJetUtility.putUnsignedByte(data, hdr + 7, nFrag + x); } else if (size + pc > usableSize) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } else { put2byte(data, pc + 2, x); } return pc + x; } addr = pc; } } if (gap + 2 + nByte > top) { pPage.defragmentPage(); top = get2byte(data, hdr + 5); assert (gap + nByte <= top); } top -= nByte; put2byte(data, hdr + 5, top); assert (top + nByte <= pPage.pBt.usableSize); return top; }
/** * 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); } } } }
/* * * Return a section of the pPage->aData to the freelist.* The first byte * of the new free block is pPage->aDisk[start]* and the size of the block * is "size" bytes.** Most of the effort here is involved in coalesing * adjacent* free blocks into a single big free block. */ private void freeSpace(int start, int size) throws SqlJetException { SqlJetMemPage pPage = this; int addr, pbegin, hdr; ISqlJetMemoryPointer data = pPage.aData; assert (pPage.pBt != null); assert (start >= pPage.hdrOffset + 6 + (pPage.leaf ? 0 : 4)); assert ((start + size) <= pPage.pBt.usableSize); assert (pPage.pBt.mutex.held()); assert (size >= 0); /* Minimum cell size is 4 */ if (ISqlJetConfig.SECURE_DELETE) { /* * Overwrite deleted information with zeros when the SECURE_DELETE* * option is enabled at compile-time */ memset(data, start, (byte) 0, size); } /* Add the space back into the linked list of freeblocks */ hdr = pPage.hdrOffset; addr = hdr + 1; while ((pbegin = get2byte(data, addr)) < start && pbegin > 0) { assert (pbegin <= pPage.pBt.usableSize - 4); if (pbegin <= addr) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } addr = pbegin; } if (pbegin > pPage.pBt.usableSize - 4) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } assert (pbegin > addr || pbegin == 0); put2byte(data, addr, start); put2byte(data, start, pbegin); put2byte(data, start + 2, size); pPage.nFree += size; /* Coalesce adjacent free blocks */ addr = pPage.hdrOffset + 1; while ((pbegin = get2byte(data, addr)) > 0) { int pnext, psize, x; assert (pbegin > addr); assert (pbegin <= pPage.pBt.usableSize - 4); pnext = get2byte(data, pbegin); psize = get2byte(data, pbegin + 2); if (pbegin + psize + 3 >= pnext && pnext > 0) { int frag = pnext - (pbegin + psize); if ((frag < 0) || (frag > (int) SqlJetUtility.getUnsignedByte(data, pPage.hdrOffset + 7))) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } SqlJetUtility.putUnsignedByte( data, pPage.hdrOffset + 7, (byte) (SqlJetUtility.getUnsignedByte(data, pPage.hdrOffset + 7) - (byte) frag)); x = get2byte(data, pnext); put2byte(data, pbegin, x); x = pnext + get2byte(data, pnext + 2) - pbegin; put2byte(data, pbegin + 2, x); } else { addr = pbegin; } } /* If the cell content area begins with a freeblock, remove it. */ if (SqlJetUtility.getUnsignedByte(data, hdr + 1) == SqlJetUtility.getUnsignedByte(data, hdr + 5) && SqlJetUtility.getUnsignedByte(data, hdr + 2) == SqlJetUtility.getUnsignedByte(data, hdr + 6)) { int top; pbegin = get2byte(data, hdr + 1); memcpy(data, hdr + 1, data, pbegin, 2); top = get2byte(data, hdr + 5) + get2byte(data, pbegin + 2); put2byte(data, hdr + 5, top); } assert (pPage.pDbPage.isWriteable()); }