/*
 * Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.util;

import java.io.Closeable;
import java.io.IOException;
import java.io.Reader;
import java.util.Arrays;
import org.h2.engine.Constants;
import org.h2.message.DbException;

This class can split SQL scripts to single SQL statements. Each SQL statement ends with the character ';', however it is ignored in comments and quotes.
/** * This class can split SQL scripts to single SQL statements. * Each SQL statement ends with the character ';', however it is ignored * in comments and quotes. */
public class ScriptReader implements Closeable { private final Reader reader; private char[] buffer;
The position in the buffer of the next char to be read
/** * The position in the buffer of the next char to be read */
private int bufferPos;
The position in the buffer of the statement start
/** * The position in the buffer of the statement start */
private int bufferStart = -1;
The position in the buffer of the last available char
/** * The position in the buffer of the last available char */
private int bufferEnd;
True if we have read past the end of file
/** * True if we have read past the end of file */
private boolean endOfFile;
True if we are inside a comment
/** * True if we are inside a comment */
private boolean insideRemark;
Only valid if insideRemark is true. True if we are inside a block comment, false if we are inside a line comment
/** * Only valid if insideRemark is true. True if we are inside a block * comment, false if we are inside a line comment */
private boolean blockRemark;
True if comments should be skipped completely by this reader.
/** * True if comments should be skipped completely by this reader. */
private boolean skipRemarks;
The position in buffer of start of comment
/** * The position in buffer of start of comment */
private int remarkStart;
Create a new SQL script reader from the given reader
Params:
  • reader – the reader
/** * Create a new SQL script reader from the given reader * * @param reader the reader */
public ScriptReader(Reader reader) { this.reader = reader; buffer = new char[Constants.IO_BUFFER_SIZE * 2]; }
Close the underlying reader.
/** * Close the underlying reader. */
@Override public void close() { try { reader.close(); } catch (IOException e) { throw DbException.convertIOException(e, null); } }
Read a statement from the reader. This method returns null if the end has been reached.
Returns:the SQL statement or null
/** * Read a statement from the reader. This method returns null if the end has * been reached. * * @return the SQL statement or null */
public String readStatement() { if (endOfFile) { return null; } try { return readStatementLoop(); } catch (IOException e) { throw DbException.convertIOException(e, null); } } private String readStatementLoop() throws IOException { bufferStart = bufferPos; int c = read(); while (true) { if (c < 0) { endOfFile = true; if (bufferPos - 1 == bufferStart) { return null; } break; } else if (c == ';') { break; } switch (c) { case '$': { c = read(); if (c == '$' && (bufferPos - bufferStart < 3 || buffer[bufferPos - 3] <= ' ')) { // dollar quoted string while (true) { c = read(); if (c < 0) { break; } if (c == '$') { c = read(); if (c < 0) { break; } if (c == '$') { break; } } } c = read(); } break; } case '\'': while (true) { c = read(); if (c < 0) { break; } if (c == '\'') { break; } } c = read(); break; case '"': while (true) { c = read(); if (c < 0) { break; } if (c == '\"') { break; } } c = read(); break; case '/': { c = read(); if (c == '*') { // block comment startRemark(true); while (true) { c = read(); if (c < 0) { break; } if (c == '*') { c = read(); if (c < 0) { clearRemark(); break; } if (c == '/') { endRemark(); break; } } } c = read(); } else if (c == '/') { // single line comment startRemark(false); while (true) { c = read(); if (c < 0) { clearRemark(); break; } if (c == '\r' || c == '\n') { endRemark(); break; } } c = read(); } break; } case '-': { c = read(); if (c == '-') { // single line comment startRemark(false); while (true) { c = read(); if (c < 0) { clearRemark(); break; } if (c == '\r' || c == '\n') { endRemark(); break; } } c = read(); } break; } default: { c = read(); } } } return new String(buffer, bufferStart, bufferPos - 1 - bufferStart); } private void startRemark(boolean block) { blockRemark = block; remarkStart = bufferPos - 2; insideRemark = true; } private void endRemark() { clearRemark(); insideRemark = false; } private void clearRemark() { if (skipRemarks) { Arrays.fill(buffer, remarkStart, bufferPos, ' '); } } private int read() throws IOException { if (bufferPos >= bufferEnd) { return readBuffer(); } return buffer[bufferPos++]; } private int readBuffer() throws IOException { if (endOfFile) { return -1; } int keep = bufferPos - bufferStart; if (keep > 0) { char[] src = buffer; if (keep + Constants.IO_BUFFER_SIZE > src.length) { // protect against NegativeArraySizeException if (src.length >= Integer.MAX_VALUE / 2) { throw new IOException("Error in parsing script, " + "statement size exceeds 1G, " + "first 80 characters of statement looks like: " + new String(buffer, bufferStart, 80)); } buffer = new char[src.length * 2]; } System.arraycopy(src, bufferStart, buffer, 0, keep); } remarkStart -= bufferStart; bufferStart = 0; bufferPos = keep; int len = reader.read(buffer, keep, Constants.IO_BUFFER_SIZE); if (len == -1) { // ensure bufferPos > bufferEnd bufferEnd = -1024; endOfFile = true; // ensure the right number of characters are read // in case the input buffer is still used bufferPos++; return -1; } bufferEnd = keep + len; return buffer[bufferPos++]; }
Check if this is the last statement, and if the single line or block comment is not finished yet.
Returns:true if the current position is inside a remark
/** * Check if this is the last statement, and if the single line or block * comment is not finished yet. * * @return true if the current position is inside a remark */
public boolean isInsideRemark() { return insideRemark; }
If currently inside a remark, this method tells if it is a block comment (true) or single line comment (false)
Returns:true if inside a block comment
/** * If currently inside a remark, this method tells if it is a block comment * (true) or single line comment (false) * * @return true if inside a block comment */
public boolean isBlockRemark() { return blockRemark; }
If comments should be skipped completely by this reader.
Params:
  • skipRemarks – true if comments should be skipped
/** * If comments should be skipped completely by this reader. * * @param skipRemarks true if comments should be skipped */
public void setSkipRemarks(boolean skipRemarks) { this.skipRemarks = skipRemarks; } }