001: /* Copyright (c) 2001-2005, The HSQL Development Group
002: * All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * Redistributions of source code must retain the above copyright notice, this
008: * list of conditions and the following disclaimer.
009: *
010: * Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * Neither the name of the HSQL Development Group nor the names of its
015: * contributors may be used to endorse or promote products derived from this
016: * software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package org.hsqldb.scriptio;
032:
033: import java.io.BufferedOutputStream;
034: import java.io.IOException;
035: import java.io.OutputStream;
036:
037: import org.hsqldb.HsqlNameManager.HsqlName;
038: import org.hsqldb.Database;
039: import org.hsqldb.DatabaseManager;
040: import org.hsqldb.DatabaseScript;
041: import org.hsqldb.HsqlException;
042: import org.hsqldb.NumberSequence;
043: import org.hsqldb.Result;
044: import org.hsqldb.Session;
045: import org.hsqldb.Table;
046: import org.hsqldb.Token;
047: import org.hsqldb.Trace;
048: import org.hsqldb.index.RowIterator;
049: import org.hsqldb.lib.FileAccess;
050: import org.hsqldb.lib.FileUtil;
051: import org.hsqldb.lib.HsqlTimer;
052: import org.hsqldb.lib.Iterator;
053:
054: //import org.hsqldb.lib.StopWatch;
055: // todo - can lock the database engine as readonly in a wrapper for this when
056: // used at checkpoint
057:
058: /**
059: * Handles all logging to file operations. A log consists of three blocks:<p>
060: *
061: * DDL BLOCK: definition of DB objects, users and rights at startup time<br>
062: * DATA BLOCK: all data for MEMORY tables at startup time<br>
063: * LOG BLOCK: SQL statements logged since startup or the last CHECKPOINT<br>
064: *
065: * The implementation of this class and its subclasses support the formats
066: * used for writing the data. In versions up to 1.7.2, this data is written
067: * to the *.script file for the database. Since 1.7.2 the data can also be
068: * written as binray in order to speed up shutdown and startup.<p>
069: *
070: * In 1.7.2, two separate files are used, one for the DDL + DATA BLOCK and
071: * the other for the LOG BLOCK.<p>
072: *
073: * A related use for this class is for saving a current snapshot of the
074: * database data to a user-defined file. This happens in the SHUTDOWN COMPACT
075: * process or done as a result of the SCRIPT command. In this case, the
076: * DATA block contains the CACHED table data as well.<p>
077: *
078: * DatabaseScriptReader and its subclasses read back the data at startup time.
079: *
080: * @author fredt@users
081: * @version 1.8.0
082: * @since 1.7.2
083: */
084: public abstract class ScriptWriterBase implements Runnable {
085:
086: Database database;
087: String outFile;
088: OutputStream fileStreamOut;
089: FileAccess.FileSync outDescriptor;
090: int tableRowCount;
091: HsqlName schemaToLog;
092:
093: /**
094: * this determines if the script is the normal script (false) used
095: * internally by the engine or a user-initiated snapshot of the DB (true)
096: */
097: boolean isDump;
098: boolean includeCachedData;
099: long byteCount;
100: volatile boolean needsSync;
101: volatile boolean forceSync;
102: volatile boolean busyWriting;
103: private int syncCount;
104: static final int INSERT = 0;
105: static final int INSERT_WITH_SCHEMA = 1;
106:
107: /** the last schema for last sessionId */
108: Session currentSession;
109: public static final String[] LIST_SCRIPT_FORMATS = new String[] {
110: Token.T_TEXT, Token.T_BINARY, null, Token.T_COMPRESSED };
111: public static final int SCRIPT_TEXT_170 = 0;
112: public static final int SCRIPT_BINARY_172 = 1;
113: public static final int SCRIPT_ZIPPED_BINARY_172 = 3;
114:
115: public static ScriptWriterBase newScriptWriter(Database db,
116: String file, boolean includeCachedData, boolean newFile,
117: int scriptType) throws HsqlException {
118:
119: if (scriptType == SCRIPT_TEXT_170) {
120: return new ScriptWriterText(db, file, includeCachedData,
121: newFile, false);
122: } else if (scriptType == SCRIPT_BINARY_172) {
123: return new ScriptWriterBinary(db, file, includeCachedData,
124: newFile);
125: } else {
126: return new ScriptWriterZipped(db, file, includeCachedData,
127: newFile);
128: }
129: }
130:
131: ScriptWriterBase() {
132: }
133:
134: ScriptWriterBase(Database db, String file,
135: boolean includeCachedData, boolean isNewFile, boolean isDump)
136: throws HsqlException {
137:
138: this .isDump = isDump;
139:
140: initBuffers();
141:
142: boolean exists = false;
143:
144: if (isDump) {
145: exists = FileUtil.exists(file);
146: } else {
147: exists = db.getFileAccess().isStreamElement(file);
148: }
149:
150: if (exists && isNewFile) {
151: throw Trace.error(Trace.FILE_IO_ERROR, file);
152: }
153:
154: this .database = db;
155: this .includeCachedData = includeCachedData;
156: outFile = file;
157: currentSession = database.sessionManager.getSysSession();
158:
159: // start with neutral schema - no SET SCHEMA to log
160: schemaToLog = currentSession.loggedSchema = currentSession.currentSchema;
161:
162: openFile();
163: }
164:
165: public void reopen() throws HsqlException {
166: openFile();
167: }
168:
169: protected abstract void initBuffers();
170:
171: /**
172: * Called internally or externally in write delay intervals.
173: */
174: public synchronized void sync() {
175:
176: if (needsSync && fileStreamOut != null) {
177: if (busyWriting) {
178: forceSync = true;
179:
180: return;
181: }
182:
183: try {
184: fileStreamOut.flush();
185: outDescriptor.sync();
186:
187: syncCount++;
188: } catch (IOException e) {
189: Trace.printSystemOut("flush() or sync() error: "
190: + e.toString());
191: }
192:
193: needsSync = false;
194: forceSync = false;
195: }
196: }
197:
198: public void close() throws HsqlException {
199:
200: stop();
201:
202: try {
203: if (fileStreamOut != null) {
204: fileStreamOut.flush();
205: outDescriptor.sync();
206: fileStreamOut.close();
207:
208: fileStreamOut = null;
209: }
210: } catch (IOException e) {
211: throw Trace.error(Trace.FILE_IO_ERROR);
212: }
213:
214: byteCount = 0;
215: }
216:
217: public long size() {
218: return byteCount;
219: }
220:
221: public void writeAll() throws HsqlException {
222:
223: try {
224: writeDDL();
225: writeExistingData();
226: finishStream();
227: } catch (IOException e) {
228: throw Trace.error(Trace.FILE_IO_ERROR);
229: }
230: }
231:
232: /**
233: * File is opened in append mode although in current usage the file
234: * never pre-exists
235: */
236: protected void openFile() throws HsqlException {
237:
238: try {
239: FileAccess fa = isDump ? FileUtil.getDefaultInstance()
240: : database.getFileAccess();
241: OutputStream fos = fa.openOutputStreamElement(outFile);
242:
243: outDescriptor = fa.getFileSync(fos);
244: fileStreamOut = new BufferedOutputStream(fos, 2 << 12);
245: } catch (IOException e) {
246: throw Trace.error(Trace.FILE_IO_ERROR, Trace.Message_Pair,
247: new Object[] { e.toString(), outFile });
248: }
249: }
250:
251: /**
252: * This is not really useful in the current usage but may be if this
253: * class is used in a different way.
254: */
255: protected void finishStream() throws IOException {
256: }
257:
258: protected void writeDDL() throws IOException, HsqlException {
259:
260: Result ddlPart = DatabaseScript.getScript(database,
261: !includeCachedData);
262:
263: writeSingleColumnResult(ddlPart);
264: }
265:
266: protected void writeExistingData() throws HsqlException,
267: IOException {
268:
269: // start with blank schema - SET SCHEMA to log
270: currentSession.loggedSchema = null;
271:
272: Iterator schemas = database.schemaManager
273: .userSchemaNameIterator();
274:
275: while (schemas.hasNext()) {
276: String schema = (String) schemas.next();
277: Iterator tables = database.schemaManager
278: .tablesIterator(schema);
279:
280: while (tables.hasNext()) {
281: Table t = (Table) tables.next();
282:
283: // write all memory table data
284: // write cached table data unless index roots have been written
285: // write all text table data apart from readonly text tables
286: // unless index roots have been written
287: boolean script = false;
288:
289: switch (t.getTableType()) {
290:
291: case Table.MEMORY_TABLE:
292: script = true;
293: break;
294:
295: case Table.CACHED_TABLE:
296: script = includeCachedData;
297: break;
298:
299: case Table.TEXT_TABLE:
300: script = includeCachedData && !t.isReadOnly();
301: break;
302: }
303:
304: try {
305: if (script) {
306: schemaToLog = t.getName().schema;
307:
308: writeTableInit(t);
309:
310: RowIterator it = t.rowIterator(currentSession);
311:
312: while (it.hasNext()) {
313: writeRow(currentSession, t, it.next()
314: .getData());
315: }
316:
317: writeTableTerm(t);
318: }
319: } catch (Exception e) {
320: throw Trace
321: .error(Trace.ASSERT_FAILED, e.toString());
322: }
323: }
324: }
325:
326: writeDataTerm();
327: }
328:
329: protected void writeTableInit(Table t) throws HsqlException,
330: IOException {
331: }
332:
333: protected void writeTableTerm(Table t) throws HsqlException,
334: IOException {
335:
336: if (t.isDataReadOnly() && !t.isTemp() && !t.isText()) {
337: StringBuffer a = new StringBuffer("SET TABLE ");
338:
339: a.append(t.getName().statementName);
340: a.append(" READONLY TRUE");
341: writeLogStatement(currentSession, a.toString());
342: }
343: }
344:
345: protected void writeSingleColumnResult(Result r)
346: throws HsqlException, IOException {
347:
348: Iterator it = r.iterator();
349:
350: while (it.hasNext()) {
351: Object[] data = (Object[]) it.next();
352:
353: writeLogStatement(currentSession, (String) data[0]);
354: }
355: }
356:
357: abstract void writeRow(Session session, Table table, Object[] data)
358: throws HsqlException, IOException;
359:
360: protected abstract void writeDataTerm() throws IOException;
361:
362: protected abstract void addSessionId(Session session)
363: throws IOException;
364:
365: public abstract void writeLogStatement(Session session, String s)
366: throws IOException, HsqlException;
367:
368: public abstract void writeInsertStatement(Session session,
369: Table table, Object[] data) throws HsqlException,
370: IOException;
371:
372: public abstract void writeDeleteStatement(Session session,
373: Table table, Object[] data) throws HsqlException,
374: IOException;
375:
376: public abstract void writeSequenceStatement(Session session,
377: NumberSequence seq) throws HsqlException, IOException;
378:
379: public abstract void writeCommitStatement(Session session)
380: throws HsqlException, IOException;
381:
382: //
383: private Object timerTask;
384:
385: // long write delay for scripts : 60s
386: protected volatile int writeDelay = 60000;
387:
388: public void run() {
389:
390: try {
391: if (writeDelay != 0) {
392: sync();
393: }
394:
395: // todo: try to do Cache.cleanUp() here, too
396: } catch (Exception e) {
397:
398: // ignore exceptions
399: // may be InterruptedException or IOException
400: if (Trace.TRACE) {
401: Trace.printSystemOut(e.toString());
402: }
403: }
404: }
405:
406: public void setWriteDelay(int delay) {
407:
408: writeDelay = delay;
409:
410: int period = writeDelay == 0 ? 1000 : writeDelay;
411:
412: HsqlTimer.setPeriod(timerTask, period);
413: }
414:
415: public void start() {
416:
417: int period = writeDelay == 0 ? 1000 : writeDelay;
418:
419: timerTask = DatabaseManager.getTimer()
420: .schedulePeriodicallyAfter(0, period, this , false);
421: }
422:
423: public void stop() {
424:
425: if (timerTask != null) {
426: HsqlTimer.cancel(timerTask);
427:
428: timerTask = null;
429: }
430: }
431:
432: public int getWriteDelay() {
433: return writeDelay;
434: }
435: }
|