/* Copyright (c) 2001-2019, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb.persist;

import java.io.UnsupportedEncodingException;

import org.hsqldb.Database;
import org.hsqldb.DatabaseType;
import org.hsqldb.HsqlException;
import org.hsqldb.Table;
import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.lib.FileUtil;
import org.hsqldb.lib.HsqlByteArrayOutputStream;
import org.hsqldb.lib.LongKeyHashMap;
import org.hsqldb.rowio.RowInputText;
import org.hsqldb.rowio.RowInputTextQuoted;
import org.hsqldb.rowio.RowOutputText;
import org.hsqldb.rowio.RowOutputTextQuoted;

// Ito Kazumitsu 20030328 - patch 1.7.2 - character encoding support
// Dimitri Maziuk - patch for NL in string support
// sqlbob@users - updated for 1.8.0 to allow new-lines in fields
// fredt@users - updated for 1.8.0 to allow correct behaviour with transactions

Acts as a buffer manager for a single TEXT table and its Row data.

Handles read/write operations on the table's text format data file using a compatible pair of org.hsqldb.rowio input/output class instances. fredt - This used to write rows as soon as they are inserted but now this is subject to transaction management. A memory buffer contains the rows not yet committed. Refactored for version 2.2.6.

Author:Bob Preston (sqlbob@users dot sourceforge.net), Fred Toussi (fredt@users dot sourceforge.net)
Version:2.5.0
Since:1.7.0
/** * Acts as a buffer manager for a single TEXT table and its Row data.<p> * * Handles read/write operations on the table's text format data file using a * compatible pair of org.hsqldb.rowio input/output class instances. * * * fredt - This used to write rows as soon as they are inserted * but now this is subject to transaction management. * A memory buffer contains the rows not yet committed. * Refactored for version 2.2.6. * * @author Bob Preston (sqlbob@users dot sourceforge.net) * @author Fred Toussi (fredt@users dot sourceforge.net) * @version 2.5.0 * @since 1.7.0 */
public class TextCache extends DataFileCache { // TextFileSettings textFileSettings; //state of Cache protected String header; protected Table table; private LongKeyHashMap uncommittedCache; HsqlByteArrayOutputStream buffer = new HsqlByteArrayOutputStream(128); //
The source string for a cached table is evaluated and the parameters are used to open the source file.

Settings are used in this order: (1) settings specified in the source string for the table (2) global database settings in *.properties file (3) program defaults

/** * The source string for a cached table is evaluated and the parameters * are used to open the source file.<p> * * Settings are used in this order: (1) settings specified in the * source string for the table (2) global database settings in * *.properties file (3) program defaults */
TextCache(Table table, String name) { super(table.database, name); this.table = table; uncommittedCache = new LongKeyHashMap(); } protected void initParams(Database database, String settingsString, boolean defrag) { this.database = database; fa = FileUtil.getFileUtil(); textFileSettings = new TextFileSettings(database.getProperties(), settingsString); dataFileName = textFileSettings.getFileName(); if (dataFileName == null) { throw Error.error(ErrorCode.X_S0501); } dataFileName = ((FileUtil) fa).canonicalOrAbsolutePath(dataFileName); maxCacheRows = textFileSettings.getMaxCacheRows(); maxCacheBytes = textFileSettings.getMaxCacheBytes(); // max size is 256G maxDataFileSize = (long) Integer.MAX_VALUE * Logger.largeDataFactor; cachedRowPadding = 1; dataFileScale = 1; } protected void initBuffers() { if (textFileSettings.isQuoted || textFileSettings.isAllQuoted) { rowIn = new RowInputTextQuoted(textFileSettings); rowOut = new RowOutputTextQuoted(textFileSettings); } else { rowIn = new RowInputText(textFileSettings); rowOut = new RowOutputText(textFileSettings); } }
Opens a data source file.
/** * Opens a data source file. */
public void open(boolean readonly) { fileFreePosition = 0; try { int type = database.getType() == DatabaseType.DB_RES ? RAFile.DATA_FILE_JAR : RAFile.DATA_FILE_TEXT; dataFile = RAFile.newScaledRAFile(database, dataFileName, readonly, type); fileFreePosition = dataFile.length(); if (fileFreePosition > maxDataFileSize) { throw Error.error(ErrorCode.DATA_FILE_IS_FULL); } initBuffers(); spaceManager = new DataSpaceManagerSimple(this, readonly); } catch (Throwable t) { throw Error.error(t, ErrorCode.FILE_IO_ERROR, ErrorCode.M_TextCache_opening_file_error, new Object[] { dataFileName, t.toString() }); } cacheReadonly = readonly; } public void release() { close(); }
Writes newly created rows to disk. In the current implementation, such rows have already been saved, so this method just removes a source file that has no rows.
/** * Writes newly created rows to disk. In the current implementation, * such rows have already been saved, so this method just removes a * source file that has no rows. */
public void close() { if (dataFile == null) { return; } writeLock.lock(); try { cache.saveAll(); boolean empty = (dataFile.length() <= textFileSettings.bytesForLineEnd.length); dataFile.synch(); dataFile.close(); dataFile = null; if (empty && !cacheReadonly) { FileUtil.getFileUtil().delete(dataFileName); } uncommittedCache.clear(); } catch (Throwable t) { throw Error.error(t, ErrorCode.FILE_IO_ERROR, ErrorCode.M_TextCache_closing_file_error, new Object[] { dataFileName, t.toString() }); } finally { writeLock.unlock(); } }
Closes the source file and deletes it if it is not read-only.
/** * Closes the source file and deletes it if it is not read-only. */
void purge() { writeLock.lock(); try { uncommittedCache.clear(); if (cacheReadonly) { release(); } else { if (dataFile != null) { dataFile.close(); dataFile = null; } FileUtil.getFileUtil().delete(dataFileName); } } catch (Throwable t) { throw Error.error(t, ErrorCode.FILE_IO_ERROR, ErrorCode.M_TextCache_purging_file_error, new Object[] { dataFileName, t.toString() }); } finally { writeLock.unlock(); } } /** * */ public void remove(CachedObject object) { writeLock.lock(); try { long pos = object.getPos(); CachedObject row = (CachedObject) uncommittedCache.remove(pos); if (row != null) { return; } cache.release(pos); } finally { writeLock.unlock(); } } public void removePersistence(CachedObject row) { writeLock.lock(); try { clearRowImage(row); } finally { writeLock.unlock(); } } private void clearRowImage(CachedObject row) { try { int length = row.getStorageSize(); int count = length - textFileSettings.bytesForLineEnd.length; rowOut.reset(); HsqlByteArrayOutputStream out = rowOut.getOutputStream(); for (; count > 0; count -= textFileSettings.bytesForSpace.length) { out.write(textFileSettings.bytesForSpace); } out.write(textFileSettings.bytesForLineEnd); dataFile.seek(row.getPos()); dataFile.write(out.getBuffer(), 0, out.size()); } catch (Throwable t) { throw Error.runtimeError(ErrorCode.U_S0500, t.getMessage()); } } public void addInit(CachedObject object) { writeLock.lock(); try { cache.put(object); } finally { writeLock.unlock(); } } public void add(CachedObject object, boolean keep) { writeLock.lock(); try { uncommittedCache.put(object.getPos(), object); } finally { writeLock.unlock(); } }
cannot use isInMemory() for text cached object
/** cannot use isInMemory() for text cached object */
public CachedObject get(CachedObject object, PersistentStore store, boolean keep) { if (object == null) { return null; } writeLock.lock(); try { CachedObject existing = cache.get(object.getPos()); if (existing != null) { return object; } try { buffer.reset(object.getStorageSize()); dataFile.seek(object.getPos()); dataFile.read(buffer.getBuffer(), 0, object.getStorageSize()); buffer.setSize(object.getStorageSize()); String rowString = buffer.toString(textFileSettings.charEncoding); ((RowInputText) rowIn).setSource(rowString, object.getPos(), buffer.size()); store.get(object, rowIn); cache.put(object); return object; } catch (Throwable t) { database.logger.logSevereEvent(dataFileName + " getFromFile problem " + object.getPos(), t); cache.clearUnchanged(); return object; } } finally { writeLock.unlock(); } } public CachedObject get(long i, PersistentStore store, boolean keep) { throw Error.runtimeError(ErrorCode.U_S0500, "TextCache"); } protected void saveRows(CachedObject[] rows, int offset, int count) { // no-op }
The row is always in uncommittedCache. Saves the row as normal and removes it
/** * The row is always in uncommittedCache. * Saves the row as normal and removes it */
public void saveRow(CachedObject row) { writeLock.lock(); try { setFileModified(); saveRowNoLock(row); uncommittedCache.remove(row.getPos()); cache.put(row); } catch (Throwable e) { database.logger.logSevereEvent("saveRow failed", e); throw Error.error(ErrorCode.DATA_FILE_ERROR, e); } finally { writeLock.unlock(); } } public String getHeader() { return header; } public void setHeaderInitialise(String header) { this.header = header; } public void setHeader(String header) { if (textFileSettings.ignoreFirst && fileFreePosition == 0) { try { writeHeader(header); this.header = header; } catch (HsqlException e) { throw new HsqlException( e, Error.getMessage(ErrorCode.GENERAL_IO_ERROR), ErrorCode.GENERAL_IO_ERROR); } return; } throw Error.error(ErrorCode.TEXT_TABLE_HEADER); } private void writeHeader(String header) { try { byte[] buf = null; String firstLine = header + TextFileSettings.NL; try { buf = firstLine.getBytes(textFileSettings.charEncoding); } catch (UnsupportedEncodingException e) { buf = firstLine.getBytes(); } dataFile.seek(0); dataFile.write(buf, 0, buf.length); fileFreePosition = buf.length; } catch (Throwable t) { throw Error.error(ErrorCode.TEXT_FILE_IO, t); } } public long getLineNumber() { return ((RowInputText) rowIn).getLineNumber(); } public TextFileSettings getTextFileSettings() { return textFileSettings; } public boolean isIgnoreFirstLine() { return textFileSettings.ignoreFirst; } protected void setFileModified() { fileModified = true; } public TextFileReader getTextFileReader() { return TextFileReader8.newTextFileReader(dataFile, textFileSettings, rowIn, cacheReadonly); } }