/** * 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; }
/** * Initialize the auxiliary information for a disk block. * * <p>Return SQLITE_OK on success. If we see that the page does not contain a well-formed database * page, then return SQLITE_CORRUPT. Note that a return of SQLITE_OK does not guarantee that the * page is well-formed. It only shows that we failed to detect any corruption. */ public void initPage() throws SqlJetException { assert (pBt != null); assert (pBt.mutex.held()); assert (pgno == pDbPage.getPageNumber()); assert (this == pDbPage.getExtra()); assert (aData.getBuffer() == pDbPage.getData().getBuffer()); if (!isInit) { int pc; /* Address of a freeblock within pPage->aData[] */ byte hdr; /* Offset to beginning of page header */ int usableSize; /* Amount of usable space on each page */ int cellOffset; /* Offset from start of page to first cell pointer */ int nFree; /* Number of unused bytes on the page */ int top; /* First byte of the cell content area */ hdr = hdrOffset; decodeFlags(SqlJetUtility.getUnsignedByte(aData, hdr)); assert (pBt.pageSize >= 512 && pBt.pageSize <= 32768); maskPage = pBt.pageSize - 1; nOverflow = 0; usableSize = pBt.usableSize; this.cellOffset = cellOffset = hdr + 12 - 4 * (leaf ? 1 : 0); top = get2byte(aData, hdr + 5); nCell = get2byte(aData, hdr + 3); if (nCell > pBt.MX_CELL()) { /* To many cells for a single page. The page must be corrupt */ throw new SqlJetException(SqlJetErrorCode.CORRUPT); } int iCellFirst = cellOffset + 2 * this.nCell; /* Compute the total free space on the page */ pc = get2byte(aData, hdr + 1); nFree = SqlJetUtility.getUnsignedByte(aData, hdr + 7) + top; // - (cellOffset + 2 * nCell); while (pc > 0) { int next, size; if (pc > usableSize - 4) { /* Free block is off the page */ throw new SqlJetException(SqlJetErrorCode.CORRUPT); } next = get2byte(aData, pc); size = get2byte(aData, pc + 2); if (next > 0 && next <= pc + size + 3) { /* Free blocks must be in accending order */ throw new SqlJetException(SqlJetErrorCode.CORRUPT); } nFree += size; pc = next; } if (nFree > usableSize) { /* Free space cannot exceed total page size */ throw new SqlJetException(SqlJetErrorCode.CORRUPT); } this.nFree = (nFree - iCellFirst); isInit = true; } }
int cellSizePtr(ISqlJetMemoryPointer pCell) { // SqlJetBtreeCellInfo info = parseCellPtr(pCell); // return info.nSize; final SqlJetMemPage pPage = this; final ISqlJetMemoryPointer pIter = pCell.getMoved(pPage.childPtrSize); int[] nSize = {0}; if (pPage.intKey) { if (pPage.hasData) { pIter.movePointer(SqlJetUtility.getVarint32(pIter, nSize)); } else { nSize[0] = 0; } /* * pIter now points at the 64-bit integer key value, a variable * length* integer. The following block moves pIter to point at the * first byte* past the end of the key value. */ int pEnd = pIter.getPointer() + 9; // while( (*pIter++)&0x80 && pIter<pEnd ); while (pIter.getPointer() < pEnd) { int b = pIter.getByteUnsigned(); pIter.movePointer(1); if ((b & 0x80) == 0) { break; } } } else { pIter.movePointer(SqlJetUtility.getVarint32(pIter, nSize)); } if (nSize[0] > pPage.maxLocal) { int minLocal = pPage.minLocal; nSize[0] = minLocal + (nSize[0] - minLocal) % (pPage.pBt.usableSize - 4); if (nSize[0] > pPage.maxLocal) { nSize[0] = minLocal; } nSize[0] += 4; } nSize[0] += (pIter.getPointer() - pCell.getPointer()); /* The minimum size of any cell is 4 bytes. */ if (nSize[0] < 4) { nSize[0] = 4; } return nSize[0]; }
@Override public Object clone() throws CloneNotSupportedException { // TODO Auto-generated method stub final SqlJetMemPage clone = (SqlJetMemPage) super.clone(); clone.aData = SqlJetUtility.allocatePtr(clone.pBt.pageSize); clone.aOvfl = aOvfl.clone(); for (int i = 0; i < aOvfl.length; i++) { clone.aOvfl[i] = (_OvflCell) aOvfl[i].clone(); } return clone; }
/** * 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()); }
/** * @author TMate Software Ltd. * @author Sergey Scherbina ([email protected]) * @author Dmitry Stadnik ([email protected]) */ public class SqlJetSchema implements ISqlJetSchema { private static final String NAME_RESERVED = "Name '%s' is reserved to internal use"; private static String AUTOINDEX = "sqlite_autoindex_%s_%d"; private static final String CANT_DELETE_IMPLICIT_INDEX = "Can't delete implicit index \"%s\""; private static final String CREATE_TABLE_SQLITE_SEQUENCE = "CREATE TABLE sqlite_sequence(name,seq)"; private static final String SQLITE_SEQUENCE = "SQLITE_SEQUENCE"; private static final Set<SqlJetBtreeTableCreateFlags> BTREE_CREATE_TABLE_FLAGS = SqlJetUtility.of(SqlJetBtreeTableCreateFlags.INTKEY, SqlJetBtreeTableCreateFlags.LEAFDATA); private static final Set<SqlJetBtreeTableCreateFlags> BTREE_CREATE_INDEX_FLAGS = SqlJetUtility.of(SqlJetBtreeTableCreateFlags.ZERODATA); private static final String TABLE_TYPE = "table"; private static final String INDEX_TYPE = "index"; private final ISqlJetDbHandle db; private final ISqlJetBtree btree; private final Map<String, ISqlJetTableDef> tableDefs = new TreeMap<String, ISqlJetTableDef>(String.CASE_INSENSITIVE_ORDER); private final Map<String, ISqlJetIndexDef> indexDefs = new TreeMap<String, ISqlJetIndexDef>(String.CASE_INSENSITIVE_ORDER); private Map<String, ISqlJetVirtualTableDef> virtualTableDefs = new TreeMap<String, ISqlJetVirtualTableDef>(String.CASE_INSENSITIVE_ORDER); private enum SqlJetSchemaObjectType { TABLE { @Override public Object getName() { return "table"; } }, INDEX { @Override public Object getName() { return "index"; } }, VIRTUAL_TABLE { @Override public Object getName() { return "virtual table"; } }; public abstract Object getName(); } public SqlJetSchema(ISqlJetDbHandle db, ISqlJetBtree btree) throws SqlJetException { this.db = db; this.btree = btree; init(); } private ISqlJetBtreeSchemaTable openSchemaTable(boolean write) throws SqlJetException { return new SqlJetBtreeSchemaTable(btree, write); } private void init() throws SqlJetException { if (db.getOptions().getSchemaVersion() == 0) return; final ISqlJetBtreeSchemaTable table = openSchemaTable(false); try { table.lock(); try { readShema(table); } finally { table.unlock(); } } finally { table.close(); } } public ISqlJetDbHandle getDb() { return db; } public ISqlJetBtree getBtree() { return btree; } public Set<String> getTableNames() throws SqlJetException { db.getMutex().enter(); try { final Set<String> s = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); s.addAll(tableDefs.keySet()); return s; } finally { db.getMutex().leave(); } } public ISqlJetTableDef getTable(String name) throws SqlJetException { db.getMutex().enter(); try { return tableDefs.get(name); } finally { db.getMutex().leave(); } } public Set<String> getIndexNames() throws SqlJetException { db.getMutex().enter(); try { final Set<String> s = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); s.addAll(indexDefs.keySet()); return s; } finally { db.getMutex().leave(); } } public ISqlJetIndexDef getIndex(String name) throws SqlJetException { db.getMutex().enter(); try { return indexDefs.get(name); } finally { db.getMutex().leave(); } } public Set<ISqlJetIndexDef> getIndexes(String tableName) throws SqlJetException { db.getMutex().enter(); try { Set<ISqlJetIndexDef> result = new HashSet<ISqlJetIndexDef>(); for (ISqlJetIndexDef index : indexDefs.values()) { if (index.getTableName().equals(tableName)) { result.add(index); } } return Collections.unmodifiableSet(result); } finally { db.getMutex().leave(); } } public Set<String> getVirtualTableNames() throws SqlJetException { db.getMutex().enter(); try { final Set<String> s = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); s.addAll(virtualTableDefs.keySet()); return s; } finally { db.getMutex().leave(); } } public ISqlJetVirtualTableDef getVirtualTable(String name) throws SqlJetException { db.getMutex().enter(); try { return virtualTableDefs.get(name); } finally { db.getMutex().leave(); } } private void readShema(ISqlJetBtreeSchemaTable table) throws SqlJetException { for (table.first(); !table.eof(); table.next()) { final String type = table.getTypeField(); if (null == type) { continue; } final String name = table.getNameField(); if (null == name) { continue; } final int page = table.getPageField(); if (TABLE_TYPE.equals(type)) { String sql = table.getSqlField(); // System.err.println(sql); final CommonTree ast = (CommonTree) parseTable(sql).getTree(); if (!isCreateVirtualTable(ast)) { final SqlJetTableDef tableDef = new SqlJetTableDef(ast, page); if (!name.equals(tableDef.getName())) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } tableDef.setRowId(table.getRowId()); tableDefs.put(name, tableDef); } else { final SqlJetVirtualTableDef virtualTableDef = new SqlJetVirtualTableDef(ast, page); if (!name.equals(virtualTableDef.getTableName())) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } virtualTableDef.setRowId(table.getRowId()); virtualTableDefs.put(name, virtualTableDef); } } else if (INDEX_TYPE.equals(type)) { final String tableName = table.getTableField(); final String sql = table.getSqlField(); if (null != sql) { // System.err.println(sql); final CommonTree ast = (CommonTree) parseIndex(sql).getTree(); final SqlJetIndexDef indexDef = new SqlJetIndexDef(ast, page); if (!name.equals(indexDef.getName())) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } if (!tableName.equals(indexDef.getTableName())) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } indexDef.setRowId(table.getRowId()); indexDefs.put(name, indexDef); } else { SqlJetBaseIndexDef indexDef = new SqlJetBaseIndexDef(name, tableName, page); indexDef.setRowId(table.getRowId()); indexDefs.put(name, indexDef); } } } bindIndexes(); } /** */ private void bindIndexes() { for (ISqlJetIndexDef indexDef : indexDefs.values()) { if (indexDef instanceof SqlJetIndexDef) { SqlJetIndexDef i = (SqlJetIndexDef) indexDef; final String tableName = i.getTableName(); final ISqlJetTableDef tableDef = tableDefs.get(tableName); if (tableDef != null) { i.bindColumns(tableDef); } } } } /** * @param ast * @return */ private boolean isCreateVirtualTable(CommonTree ast) { final CommonTree optionsNode = (CommonTree) ast.getChild(0); for (int i = 0; i < optionsNode.getChildCount(); i++) { CommonTree optionNode = (CommonTree) optionsNode.getChild(i); if ("virtual".equalsIgnoreCase(optionNode.getText())) { return true; } } return false; } private RuleReturnScope parseTable(String sql) throws SqlJetException { try { CharStream chars = new ANTLRStringStream(sql); SqlLexer lexer = new SqlLexer(chars); CommonTokenStream tokens = new CommonTokenStream(lexer); SqlParser parser = new SqlParser(tokens); return parser.schema_create_table_stmt(); } catch (RecognitionException re) { throw new SqlJetException(SqlJetErrorCode.ERROR, "Invalid sql statement: " + sql); } } private ParserRuleReturnScope parseIndex(String sql) throws SqlJetException { try { CharStream chars = new ANTLRStringStream(sql); SqlLexer lexer = new SqlLexer(chars); CommonTokenStream tokens = new CommonTokenStream(lexer); SqlParser parser = new SqlParser(tokens); return parser.create_index_stmt(); } catch (RecognitionException re) { throw new SqlJetException(SqlJetErrorCode.ERROR, "Invalid sql statement: " + sql); } } @Override public String toString() { db.getMutex().enter(); try { StringBuffer buffer = new StringBuffer(); buffer.append("Tables:\n"); for (ISqlJetTableDef tableDef : tableDefs.values()) { buffer.append(tableDef.toString()); buffer.append('\n'); } buffer.append("Indexes:\n"); for (ISqlJetIndexDef indexDef : indexDefs.values()) { buffer.append(indexDef.toString()); buffer.append('\n'); } return buffer.toString(); } finally { db.getMutex().leave(); } } public ISqlJetTableDef createTable(String sql) throws SqlJetException { db.getMutex().enter(); try { return createTableSafe(sql, false); } finally { db.getMutex().leave(); } } private ISqlJetTableDef createTableSafe(String sql, boolean internal) throws SqlJetException { final RuleReturnScope parseTable = parseTable(sql); final CommonTree ast = (CommonTree) parseTable.getTree(); if (isCreateVirtualTable(ast)) { throw new SqlJetException(SqlJetErrorCode.ERROR); } final SqlJetTableDef tableDef = new SqlJetTableDef(ast, 0); if (null == tableDef.getName()) throw new SqlJetException(SqlJetErrorCode.ERROR); final String tableName = tableDef.getName(); if ("".equals(tableName)) throw new SqlJetException(SqlJetErrorCode.ERROR); if (!internal) { checkNameReserved(tableName); } if (tableDefs.containsKey(tableName)) { if (tableDef.isKeepExisting()) { return tableDefs.get(tableName); } else { throw new SqlJetException( SqlJetErrorCode.ERROR, "Table \"" + tableName + "\" exists already"); } } checkNameConflict(SqlJetSchemaObjectType.TABLE, tableName); checkFieldNamesRepeatsConflict(tableDef.getName(), tableDef.getColumns()); final List<ISqlJetColumnDef> columns = tableDef.getColumns(); if (null == columns || 0 == columns.size()) throw new SqlJetException(SqlJetErrorCode.ERROR); final String createTableSql = getCreateTableSql(parseTable); final ISqlJetBtreeSchemaTable schemaTable = openSchemaTable(true); try { schemaTable.lock(); try { db.getOptions().changeSchemaVersion(); final int page = btree.createTable(BTREE_CREATE_TABLE_FLAGS); final long rowId = schemaTable.insertRecord(TABLE_TYPE, tableName, tableName, page, createTableSql); addConstraints(schemaTable, tableDef); tableDef.setPage(page); tableDef.setRowId(rowId); tableDefs.put(tableName, tableDef); return tableDef; } finally { schemaTable.unlock(); } } finally { schemaTable.close(); } } /** * @param tableDef * @throws SqlJetException */ private void checkFieldNamesRepeatsConflict(String tableName, List<ISqlJetColumnDef> columns) throws SqlJetException { final Set<String> names = new HashSet<String>(); for (ISqlJetColumnDef columnDef : columns) { final String name = columnDef.getName(); if (names.contains(name)) { throw new SqlJetException( SqlJetErrorCode.ERROR, String.format( "Definition for table '%s' has conflict of repeating fields named '%s'", tableName, name)); } else { names.add(name); } } } /** * @param parseTable * @return */ private String getCreateTableSql(RuleReturnScope parseTable) { return String.format("CREATE TABLE %s", getCoreSQL(parseTable)); } /** * @param parseIndex * @return */ private String getCreateIndexSql(RuleReturnScope parseIndex) { return String.format("CREATE INDEX %s", getCoreSQL(parseIndex)); } /** * @param parseIndex * @return */ private String getCreateIndexUniqueSql(RuleReturnScope parseIndex) { return String.format("CREATE UNIQUE INDEX %s", getCoreSQL(parseIndex)); } /** * @param parseTable * @return */ private String getCreateVirtualTableSql(RuleReturnScope parseTable) { return String.format("CREATE VIRTUAL TABLE %s", getCoreSQL(parseTable)); } private String getCoreSQL(RuleReturnScope parsedSQL) { final CommonTree ast = (CommonTree) parsedSQL.getTree(); final CommonToken nameToken = (CommonToken) ((CommonTree) ast.getChild(1)).getToken(); final CharStream inputStream = nameToken.getInputStream(); final CommonToken stopToken = (CommonToken) parsedSQL.getStop(); return inputStream.substring(nameToken.getStartIndex(), stopToken.getStopIndex()); } /** * @param i * @return */ private String generateAutoIndexName(String tableName, int i) { return String.format(AUTOINDEX, tableName, i); } /** * @param schemaTable * @param tableDef * @throws SqlJetException */ private void addConstraints(ISqlJetBtreeSchemaTable schemaTable, final SqlJetTableDef tableDef) throws SqlJetException { final String tableName = tableDef.getName(); final List<ISqlJetColumnDef> columns = tableDef.getColumns(); int i = 0; for (final ISqlJetColumnDef column : columns) { final List<ISqlJetColumnConstraint> constraints = column.getConstraints(); if (null == constraints) continue; for (final ISqlJetColumnConstraint constraint : constraints) { if (constraint instanceof ISqlJetColumnPrimaryKey) { final ISqlJetColumnPrimaryKey pk = (ISqlJetColumnPrimaryKey) constraint; if (!column.hasExactlyIntegerType()) { if (pk.isAutoincremented()) { throw new SqlJetException( SqlJetErrorCode.ERROR, "AUTOINCREMENT is allowed only for INTEGER PRIMARY KEY fields"); } createAutoIndex(schemaTable, tableName, generateAutoIndexName(tableName, ++i)); } else if (pk.isAutoincremented()) { checkSequenceTable(); } } else if (constraint instanceof ISqlJetColumnUnique) { createAutoIndex(schemaTable, tableName, generateAutoIndexName(tableName, ++i)); } } } final List<ISqlJetTableConstraint> constraints = tableDef.getConstraints(); if (null != constraints) { for (final ISqlJetTableConstraint constraint : constraints) { if (constraint instanceof ISqlJetTablePrimaryKey) { boolean b = false; final ISqlJetTablePrimaryKey pk = (ISqlJetTablePrimaryKey) constraint; if (pk.getColumns().size() == 1) { final String n = pk.getColumns().get(0); final ISqlJetColumnDef c = tableDef.getColumn(n); b = c != null && c.hasExactlyIntegerType(); } if (!b) { createAutoIndex(schemaTable, tableName, generateAutoIndexName(tableName, ++i)); } } else if (constraint instanceof ISqlJetTableUnique) { createAutoIndex(schemaTable, tableName, generateAutoIndexName(tableName, ++i)); } } } } /** * @param schemaTable * @throws SqlJetException */ private void checkSequenceTable() throws SqlJetException { if (!tableDefs.containsKey(SQLITE_SEQUENCE)) { createTableSafe(CREATE_TABLE_SQLITE_SEQUENCE, true); } } /** @throws SqlJetException */ public ISqlJetBtreeDataTable openSequenceTable() throws SqlJetException { if (tableDefs.containsKey(SQLITE_SEQUENCE)) { return new SqlJetBtreeDataTable(btree, SQLITE_SEQUENCE, true); } else { return null; } } /** * @param schemaTable * @param generateAutoIndexName * @throws SqlJetException */ private SqlJetBaseIndexDef createAutoIndex( ISqlJetBtreeSchemaTable schemaTable, String tableName, String autoIndexName) throws SqlJetException { final int page = btree.createTable(BTREE_CREATE_INDEX_FLAGS); final SqlJetBaseIndexDef indexDef = new SqlJetBaseIndexDef(autoIndexName, tableName, page); indexDef.setRowId(schemaTable.insertRecord(INDEX_TYPE, autoIndexName, tableName, page, null)); indexDefs.put(autoIndexName, indexDef); return indexDef; } public ISqlJetIndexDef createIndex(String sql) throws SqlJetException { db.getMutex().enter(); try { return createIndexSafe(sql); } finally { db.getMutex().leave(); } } private ISqlJetIndexDef createIndexSafe(String sql) throws SqlJetException { final ParserRuleReturnScope parseIndex = parseIndex(sql); final CommonTree ast = (CommonTree) parseIndex.getTree(); final SqlJetIndexDef indexDef = new SqlJetIndexDef(ast, 0); if (null == indexDef.getName()) throw new SqlJetException(SqlJetErrorCode.ERROR); final String indexName = indexDef.getName(); if ("".equals(indexName)) throw new SqlJetException(SqlJetErrorCode.ERROR); checkNameReserved(indexName); if (indexDefs.containsKey(indexName)) { if (indexDef.isKeepExisting()) { return indexDefs.get(indexName); } else { throw new SqlJetException( SqlJetErrorCode.ERROR, "Index \"" + indexName + "\" exists already"); } } checkNameConflict(SqlJetSchemaObjectType.INDEX, indexName); if (null == indexDef.getTableName()) throw new SqlJetException(SqlJetErrorCode.ERROR); final String tableName = indexDef.getTableName(); if ("".equals(tableName)) throw new SqlJetException(SqlJetErrorCode.ERROR); final List<ISqlJetIndexedColumn> columns = indexDef.getColumns(); if (null == columns) throw new SqlJetException(SqlJetErrorCode.ERROR); final ISqlJetTableDef tableDef = getTable(tableName); if (null == tableDef) throw new SqlJetException(SqlJetErrorCode.ERROR); for (final ISqlJetIndexedColumn column : columns) { if (null == column.getName()) throw new SqlJetException(SqlJetErrorCode.ERROR); final String columnName = column.getName(); if ("".equals(columnName)) throw new SqlJetException(SqlJetErrorCode.ERROR); if (null == tableDef.getColumn(columnName)) throw new SqlJetException( SqlJetErrorCode.ERROR, "Column \"" + columnName + "\" not found in table \"" + tableName + "\""); } final ISqlJetBtreeSchemaTable schemaTable = openSchemaTable(true); final String createIndexSQL = indexDef.isUnique() ? getCreateIndexUniqueSql(parseIndex) : getCreateIndexSql(parseIndex); try { schemaTable.lock(); try { db.getOptions().changeSchemaVersion(); final int page = btree.createTable(BTREE_CREATE_INDEX_FLAGS); final long rowId = schemaTable.insertRecord(INDEX_TYPE, indexName, tableName, page, createIndexSQL); indexDef.setPage(page); indexDef.setRowId(rowId); indexDef.bindColumns(tableDef); indexDefs.put(indexName, indexDef); final SqlJetBtreeIndexTable indexTable = new SqlJetBtreeIndexTable(btree, indexDef.getName(), true); try { indexTable.reindex(this); } finally { indexTable.close(); } return indexDef; } finally { schemaTable.unlock(); } } finally { schemaTable.close(); } } public void dropTable(String tableName) throws SqlJetException { db.getMutex().enter(); try { dropTableSafe(tableName); } finally { db.getMutex().leave(); } } private void dropTableSafe(String tableName) throws SqlJetException { if (null == tableName || "".equals(tableName)) throw new SqlJetException(SqlJetErrorCode.MISUSE, "Table name must be not empty"); if (!tableDefs.containsKey(tableName)) throw new SqlJetException(SqlJetErrorCode.MISUSE, "Table not found: " + tableName); final SqlJetTableDef tableDef = (SqlJetTableDef) tableDefs.get(tableName); dropTableIndexes(tableDef); final ISqlJetBtreeSchemaTable schemaTable = openSchemaTable(true); try { schemaTable.lock(); try { db.getOptions().changeSchemaVersion(); if (!schemaTable.goToRow(tableDef.getRowId()) || !TABLE_TYPE.equals(schemaTable.getTypeField())) throw new SqlJetException(SqlJetErrorCode.CORRUPT); final String n = schemaTable.getNameField(); if (null == n || !tableName.equals(n)) throw new SqlJetException(SqlJetErrorCode.CORRUPT); schemaTable.delete(); } finally { schemaTable.unlock(); } } finally { schemaTable.close(); } final int page = tableDef.getPage(); final int moved = btree.dropTable(page); if (moved != 0) { movePage(page, moved); } tableDefs.remove(tableName); } /** * @param schemaTable * @param tableDef * @throws SqlJetException */ private void dropTableIndexes(SqlJetTableDef tableDef) throws SqlJetException { final String tableName = tableDef.getName(); final Iterator<Map.Entry<String, ISqlJetIndexDef>> iterator = indexDefs.entrySet().iterator(); while (iterator.hasNext()) { final Map.Entry<String, ISqlJetIndexDef> indexDefEntry = iterator.next(); final String indexName = indexDefEntry.getKey(); final ISqlJetIndexDef indexDef = indexDefEntry.getValue(); if (indexDef.getTableName().equals(tableName)) { if (doDropIndex(indexName, true, false)) { iterator.remove(); } } } } /** * @param schemaTable * @param name * @param generateAutoIndexName * @throws SqlJetException */ private boolean doDropIndex(String indexName, boolean allowAutoIndex, boolean throwIfFial) throws SqlJetException { if (!indexDefs.containsKey(indexName)) { if (throwIfFial) throw new SqlJetException(SqlJetErrorCode.MISUSE); return false; } final SqlJetBaseIndexDef indexDef = (SqlJetBaseIndexDef) indexDefs.get(indexName); if (!allowAutoIndex && indexDef.isImplicit()) { if (throwIfFial) throw new SqlJetException( SqlJetErrorCode.MISUSE, String.format(CANT_DELETE_IMPLICIT_INDEX, indexName)); return false; } final ISqlJetBtreeSchemaTable schemaTable = openSchemaTable(true); try { schemaTable.lock(); try { if (!schemaTable.goToRow(indexDef.getRowId()) || !INDEX_TYPE.equals(schemaTable.getTypeField())) { if (throwIfFial) throw new SqlJetException(SqlJetErrorCode.INTERNAL); return false; } final String n = schemaTable.getNameField(); if (null == n || !indexName.equals(n)) { if (throwIfFial) throw new SqlJetException(SqlJetErrorCode.INTERNAL); return false; } if (!allowAutoIndex && schemaTable.isNull(ISqlJetBtreeSchemaTable.SQL_FIELD)) { if (throwIfFial) throw new SqlJetException( SqlJetErrorCode.MISUSE, String.format(CANT_DELETE_IMPLICIT_INDEX, indexName)); return false; } schemaTable.delete(); } finally { schemaTable.unlock(); } } finally { schemaTable.close(); } final int page = indexDef.getPage(); final int moved = btree.dropTable(page); if (moved != 0) { movePage(page, moved); } return true; } /** * @param page * @param moved * @throws SqlJetException */ private void movePage(final int page, final int moved) throws SqlJetException { final ISqlJetBtreeSchemaTable schemaTable = openSchemaTable(true); try { schemaTable.lock(); try { for (schemaTable.first(); !schemaTable.eof(); schemaTable.next()) { final long pageField = schemaTable.getPageField(); if (pageField == moved) { final String nameField = schemaTable.getNameField(); schemaTable.updateRecord( schemaTable.getRowId(), schemaTable.getTypeField(), nameField, schemaTable.getTableField(), page, schemaTable.getSqlField()); final ISqlJetIndexDef index = getIndex(nameField); if (index != null) { if (index instanceof SqlJetBaseIndexDef) { ((SqlJetBaseIndexDef) index).setPage(page); } } else { final ISqlJetTableDef table = getTable(nameField); if (table != null) { if (table instanceof SqlJetTableDef) { ((SqlJetTableDef) table).setPage(page); } } } return; } } } finally { schemaTable.unlock(); } } finally { schemaTable.close(); } } public void dropIndex(String indexName) throws SqlJetException { db.getMutex().enter(); try { dropIndexSafe(indexName); } finally { db.getMutex().leave(); } } private void dropIndexSafe(String indexName) throws SqlJetException { if (null == indexName || "".equals(indexName)) throw new SqlJetException(SqlJetErrorCode.MISUSE, "Index name must be not empty"); if (!indexDefs.containsKey(indexName)) throw new SqlJetException(SqlJetErrorCode.MISUSE, "Index not found: " + indexName); if (doDropIndex(indexName, false, true)) { db.getOptions().changeSchemaVersion(); indexDefs.remove(indexName); } } /** * @param tableName * @param newTableName * @param newColumnDef * @return * @throws SqlJetException */ private ISqlJetTableDef alterTableSafe(final SqlJetAlterTableDef alterTableDef) throws SqlJetException { assert (null != alterTableDef); String tableName = alterTableDef.getTableName(); String newTableName = alterTableDef.getNewTableName(); ISqlJetColumnDef newColumnDef = alterTableDef.getNewColumnDef(); if (null == tableName) { throw new SqlJetException(SqlJetErrorCode.MISUSE, "Table name isn't defined"); } if (null == newTableName && null == newColumnDef) { throw new SqlJetException(SqlJetErrorCode.MISUSE, "Not defined any altering"); } boolean renameTable = false; if (null != newTableName) { renameTable = true; } else { newTableName = tableName; } if (renameTable && tableDefs.containsKey(newTableName)) { throw new SqlJetException( SqlJetErrorCode.MISUSE, String.format("Table \"%s\" already exists", newTableName)); } final SqlJetTableDef tableDef = (SqlJetTableDef) tableDefs.get(tableName); if (null == tableDef) { throw new SqlJetException( SqlJetErrorCode.MISUSE, String.format("Table \"%s\" not found", tableName)); } List<ISqlJetColumnDef> columns = tableDef.getColumns(); if (null != newColumnDef) { final String fieldName = newColumnDef.getName(); if (tableDef.getColumn(fieldName) != null) { throw new SqlJetException( SqlJetErrorCode.MISUSE, String.format("Field \"%s\" already exists in table \"%s\"", fieldName, tableName)); } final List<ISqlJetColumnConstraint> constraints = newColumnDef.getConstraints(); if (null != constraints && 0 != constraints.size()) { boolean notNull = false; boolean defaultValue = false; for (final ISqlJetColumnConstraint constraint : constraints) { if (constraint instanceof ISqlJetColumnNotNull) { notNull = true; } else if (constraint instanceof ISqlJetColumnDefault) { defaultValue = true; } else { throw new SqlJetException( SqlJetErrorCode.MISUSE, String.format("Invalid constraint: %s", constraint.toString())); } } if (notNull && !defaultValue) { throw new SqlJetException( SqlJetErrorCode.MISUSE, "NOT NULL requires to have DEFAULT value"); } } columns = new ArrayList<ISqlJetColumnDef>(columns); columns.add(newColumnDef); } final int page = tableDef.getPage(); final long rowId = tableDef.getRowId(); final SqlJetTableDef alterDef = new SqlJetTableDef( newTableName, null, tableDef.isTemporary(), false, columns, tableDef.getConstraints(), page, rowId); final ISqlJetBtreeSchemaTable schemaTable = openSchemaTable(true); try { schemaTable.lock(); try { if (!schemaTable.goToRow(rowId)) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } final String typeField = schemaTable.getTypeField(); final String nameField = schemaTable.getNameField(); final String tableField = schemaTable.getTableField(); final int pageField = schemaTable.getPageField(); if (null == typeField || !TABLE_TYPE.equals(typeField)) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } if (null == nameField || !tableName.equals(nameField)) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } if (null == tableField || !tableName.equals(tableField)) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } if (0 == pageField || pageField != page) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } final String alteredSql = getTableAlteredSql(schemaTable.getSqlField(), alterTableDef); db.getOptions().changeSchemaVersion(); schemaTable.insertRecord(TABLE_TYPE, newTableName, newTableName, page, alteredSql); if (renameTable && !tableName.equals(newTableName)) { renameTablesIndices( schemaTable, tableName, newTableName, getAlterTableName(alterTableDef)); } tableDefs.remove(tableName); tableDefs.put(newTableName, alterDef); return alterDef; } finally { schemaTable.unlock(); } } finally { schemaTable.close(); } } /** * @param alterTableDef * @return */ private String getAlterTableName(SqlJetAlterTableDef alterTableDef) { final ParserRuleReturnScope parsedSql = alterTableDef.getParsedSql(); final CommonTree ast = (CommonTree) parsedSql.getTree(); final CommonToken stopToken = (CommonToken) parsedSql.getStop(); final CommonToken nameToken = (CommonToken) ((CommonTree) ast.getChild(ast.getChildCount() - 1)).getToken(); final CharStream inputStream = nameToken.getInputStream(); return inputStream.substring(nameToken.getStartIndex(), stopToken.getStopIndex()); } /** * @param sql * @param alterTableDef * @return * @throws SqlJetException */ private String getTableAlteredSql(String sql, SqlJetAlterTableDef alterTableDef) throws SqlJetException { final RuleReturnScope parsedSQL = parseTable(sql); final CommonTree ast = (CommonTree) parsedSQL.getTree(); final CommonToken nameToken = (CommonToken) ((CommonTree) ast.getChild(1)).getToken(); final CharStream inputStream = nameToken.getInputStream(); final CommonToken stopToken = (CommonToken) parsedSQL.getStop(); final StringBuilder b = new StringBuilder(); if (alterTableDef.getNewTableName() != null) { b.append(inputStream.substring(0, nameToken.getStartIndex() - 1)); b.append(getAlterTableName(alterTableDef)); b.append(inputStream.substring(nameToken.getStopIndex() + 1, stopToken.getStopIndex())); } else if (alterTableDef.getNewColumnDef() != null) { b.append(inputStream.substring(0, stopToken.getStartIndex() - 1)); b.append(",").append(getAlterTableName(alterTableDef)); b.append(inputStream.substring(stopToken.getStartIndex(), stopToken.getStopIndex())); } else { throw new SqlJetException("Wrong ALTER TABLE statement"); } return b.toString(); } /** * @param schemaTable * @param newTableName * @param tableName * @param string * @throws SqlJetException */ private void renameTablesIndices( final ISqlJetBtreeSchemaTable schemaTable, String tableName, String newTableName, String alterTableName) throws SqlJetException { final Set<ISqlJetIndexDef> indexes = getIndexes(tableName); if (null == indexes || 0 == indexes.size()) { return; } int i = 0; for (final ISqlJetIndexDef index : indexes) { if (index instanceof SqlJetBaseIndexDef) { final SqlJetBaseIndexDef indexDef = (SqlJetBaseIndexDef) index; final String indexName = indexDef.getName(); final long rowId = indexDef.getRowId(); final int page = indexDef.getPage(); if (!schemaTable.goToRow(rowId)) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } final String typeField = schemaTable.getTypeField(); final String nameField = schemaTable.getNameField(); final String tableField = schemaTable.getTableField(); final int pageField = schemaTable.getPageField(); if (null == typeField || !INDEX_TYPE.equals(typeField)) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } if (null == nameField || !indexName.equals(nameField)) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } if (null == tableField || !tableName.equals(tableField)) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } if (0 == pageField || pageField != page) { throw new SqlJetException(SqlJetErrorCode.CORRUPT); } indexDef.setTableName(newTableName); String newIndexName = indexName; String alteredIndexSql = null; if (index.isImplicit()) { newIndexName = generateAutoIndexName(tableName, ++i); indexDef.setName(newIndexName); indexDefs.remove(indexName); indexDefs.put(newIndexName, indexDef); } else { alteredIndexSql = getAlteredIndexSql(schemaTable.getSqlField(), alterTableName); } schemaTable.insertRecord(INDEX_TYPE, newIndexName, newTableName, page, alteredIndexSql); } else { throw new SqlJetException(SqlJetErrorCode.INTERNAL); } } } /** * @param sql * @param alterTableName * @return * @throws SqlJetException */ private String getAlteredIndexSql(String sql, String alterTableName) throws SqlJetException { final RuleReturnScope parsedSQL = parseIndex(sql); final CommonTree ast = (CommonTree) parsedSQL.getTree(); final CommonToken nameToken = (CommonToken) ((CommonTree) ast.getChild(2)).getToken(); final CharStream inputStream = nameToken.getInputStream(); final CommonToken stopToken = (CommonToken) parsedSQL.getStop(); final StringBuilder b = new StringBuilder(); b.append(inputStream.substring(0, nameToken.getStartIndex() - 1)); b.append(alterTableName); b.append(inputStream.substring(nameToken.getStopIndex() + 1, stopToken.getStopIndex())); return b.toString(); } private ParserRuleReturnScope parseSqlStatement(String sql) throws SqlJetException { try { CharStream chars = new ANTLRStringStream(sql); SqlLexer lexer = new SqlLexer(chars); CommonTokenStream tokens = new CommonTokenStream(lexer); SqlParser parser = new SqlParser(tokens); return parser.sql_stmt_itself(); } catch (RecognitionException re) { throw new SqlJetException(SqlJetErrorCode.ERROR, "Invalid sql statement: " + sql); } } public ISqlJetTableDef alterTable(String sql) throws SqlJetException { final SqlJetAlterTableDef alterTableDef = new SqlJetAlterTableDef(parseSqlStatement(sql)); db.getMutex().enter(); try { return alterTableSafe(alterTableDef); } finally { db.getMutex().leave(); } } public ISqlJetVirtualTableDef createVirtualTable(String sql, int page) throws SqlJetException { db.getMutex().enter(); try { return createVirtualTableSafe(sql, page); } finally { db.getMutex().leave(); } } private ISqlJetVirtualTableDef createVirtualTableSafe(String sql, int page) throws SqlJetException { final RuleReturnScope parseTable = parseTable(sql); final CommonTree ast = (CommonTree) parseTable.getTree(); if (!isCreateVirtualTable(ast)) { throw new SqlJetException(SqlJetErrorCode.ERROR); } final SqlJetVirtualTableDef tableDef = new SqlJetVirtualTableDef(ast, 0); if (null == tableDef.getTableName()) throw new SqlJetException(SqlJetErrorCode.ERROR); final String tableName = tableDef.getTableName(); if ("".equals(tableName)) { throw new SqlJetException(SqlJetErrorCode.ERROR); } checkNameReserved(tableName); checkFieldNamesRepeatsConflict(tableDef.getTableName(), tableDef.getModuleColumns()); if (virtualTableDefs.containsKey(tableName)) { throw new SqlJetException( SqlJetErrorCode.ERROR, "Virtual table \"" + tableName + "\" exists already"); } checkNameConflict(SqlJetSchemaObjectType.VIRTUAL_TABLE, tableName); final ISqlJetBtreeSchemaTable schemaTable = openSchemaTable(true); final String createVirtualTableSQL = getCreateVirtualTableSql(parseTable); try { schemaTable.lock(); try { db.getOptions().changeSchemaVersion(); final long rowId = schemaTable.insertRecord(TABLE_TYPE, tableName, tableName, page, createVirtualTableSQL); tableDef.setPage(page); tableDef.setRowId(rowId); virtualTableDefs.put(tableName, tableDef); return tableDef; } finally { schemaTable.unlock(); } } finally { schemaTable.close(); } } /** * @param name * @throws SqlJetException */ private void checkNameReserved(final String name) throws SqlJetException { if (isNameReserved(name)) { throw new SqlJetException(String.format(NAME_RESERVED, name)); } } /** * Returns true if name is reserved for internal use. * * @param name * @return true if name is reserved */ public boolean isNameReserved(String name) { return name.startsWith("sqlite_"); } /** * @param tableName * @throws SqlJetException */ private void checkNameConflict(SqlJetSchemaObjectType objectType, final String tableName) throws SqlJetException { if (isNameConflict(objectType, tableName)) { throw new SqlJetException( String.format( "Name conflict: %s named '%s' exists already", objectType.getName(), tableName)); } } private boolean isNameConflict(SqlJetSchemaObjectType objectType, String name) { if (objectType != SqlJetSchemaObjectType.TABLE) { if (tableDefs.containsKey(name)) { return true; } } if (objectType != SqlJetSchemaObjectType.INDEX) { if (indexDefs.containsKey(name)) { return true; } } if (objectType != SqlJetSchemaObjectType.VIRTUAL_TABLE) { if (virtualTableDefs.containsKey(name)) { return true; } } return false; } public ISqlJetIndexDef createIndexForVirtualTable( final String virtualTableName, final String indexName) throws SqlJetException { db.getMutex().enter(); try { return createIndexForVirtualTableSafe(virtualTableName, indexName); } finally { db.getMutex().leave(); } } /** * @param virtualTableName * @param indexName * @return * @throws SqlJetException */ private ISqlJetIndexDef createIndexForVirtualTableSafe(String virtualTableName, String indexName) throws SqlJetException { if (null == virtualTableName || "".equals(virtualTableName)) throw new SqlJetException(SqlJetErrorCode.ERROR); if (null == indexName || "".equals(indexName)) throw new SqlJetException(SqlJetErrorCode.ERROR); checkNameReserved(indexName); if (indexDefs.containsKey(indexName)) { throw new SqlJetException( SqlJetErrorCode.ERROR, "Index \"" + indexName + "\" exists already"); } checkNameConflict(SqlJetSchemaObjectType.INDEX, indexName); final ISqlJetVirtualTableDef tableDef = getVirtualTable(virtualTableName); if (null == tableDef) throw new SqlJetException(SqlJetErrorCode.ERROR); final ISqlJetBtreeSchemaTable schemaTable = openSchemaTable(true); try { schemaTable.lock(); try { db.getOptions().changeSchemaVersion(); final ISqlJetIndexDef indexDef = createAutoIndex(schemaTable, tableDef.getTableName(), indexName); indexDefs.put(indexName, indexDef); return indexDef; } finally { schemaTable.unlock(); } } finally { schemaTable.close(); } } }