org.h2.util.ScriptReader Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2004-2022 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (https://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.
*/
public class ScriptReader implements Closeable {
private final Reader reader;
private char[] buffer;
/**
* The position in the buffer of the next char to be read
*/
private int bufferPos;
/**
* The position in the buffer of the statement start
*/
private int bufferStart = -1;
/**
* The position in the buffer of the last available char
*/
private int bufferEnd;
/**
* True if we have read past the end of file
*/
private boolean endOfFile;
/**
* 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
*/
private boolean blockRemark;
/**
* True if comments should be skipped completely by this reader.
*/
private boolean skipRemarks;
/**
* The position in buffer of start of comment
*/
private int remarkStart;
/**
* 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.
*/
@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.
*
* @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);
int level = 1;
while (true) {
c = read();
if (c < 0) {
break;
}
if (c == '*') {
c = read();
if (c < 0) {
clearRemark();
break;
}
if (c == '/') {
if (--level == 0) {
endRemark();
break;
}
}
} else if (c == '/') {
c = read();
if (c < 0) {
clearRemark();
break;
}
if (c == '*') {
level++;
}
}
}
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.
*
* @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)
*
* @return true if inside a block comment
*/
public boolean isBlockRemark() {
return blockRemark;
}
/**
* 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;
}
}