/* * Copyright 2004-2010 H2 Group. Multiple-Licensed under the H2 License, * Version 1.0, and under the Eclipse Public License, Version 1.0 * (http://h2database.com/html/license.html). * Initial Developer: H2 Group */ package org.teiid.odbc; import java.io.IOException; import java.io.Reader; import java.io.StringReader; /** * 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 { private Reader reader; private StringBuilder builder; private boolean endOfFile; private boolean insideRemark; private boolean blockRemark; private boolean rewrite; private int expressionStart=-1; private int expressionEnd=-1; /** * Create a new SQL script reader from the given reader * * @param reader the reader */ public ScriptReader(Reader reader) { this.reader = reader; this.builder = new StringBuilder(1<<13); } public ScriptReader(String string) { this.reader = new StringReader(string); this.builder = new StringBuilder(string.length()); } /** * Close the underlying reader. */ public void close() throws IOException{ reader.close(); } /** * 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() throws IOException{ if (endOfFile) { return null; } while (true) { String result = readStatementLoop(); if (result != null || endOfFile) { return result; } } } private String readStatementLoop() throws IOException { int c = read(); while (true) { if (c < 0) { endOfFile = true; break; } else if (c == ';') { builder.setLength(builder.length()-1); break; } switch (c) { case '$': { c = read(); if (c == '$' && (builder.length() < 3 || builder.charAt(builder.length() - 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 '\'': //TODO: in rewrite mode could handle the E' logic here rather than in the parser //however the parser now uses E' with like to detect pg specific handling if (expressionEnd != builder.length() - 1) { expressionStart = builder.length() - 1; } while (true) { c = read(); if (c < 0 || c == '\'') { break; } } expressionEnd = builder.length(); 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(false); while (true) { c = read(); if (c < 0) { break; } if (c == '*') { c = read(); if (c < 0) { break; } if (c == '/') { endRemark(); break; } } } c = read(); } else if (c == '/') { // single line comment startRemark(false); while (true) { c = read(); if (c < 0) { 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) { break; } if (c == '\r' || c == '\n') { endRemark(); break; } } c = read(); } break; } case ':': { if (rewrite) { int start = builder.length(); c = read(); if (c == ':') { while (true) { c = read(); if (c < 0 || !Character.isLetterOrDigit(c)) { String type = builder.substring(start+1, builder.length() - (c<0?0:1)); builder.setLength(start-1); if (expressionStart != -1 && expressionEnd == start -1) { //special handling for regclass cast - it won't always work if ("regclass".equalsIgnoreCase(type)) { //$NON-NLS-1$ builder.insert(expressionStart, "regclass("); //$NON-NLS-1$ builder.append(")"); //$NON-NLS-1$ } else if ("regproc".equalsIgnoreCase(type)) { //$NON-NLS-1$ String name = builder.substring(expressionStart); if (name.startsWith("'\"") && name.length() > 4) { //$NON-NLS-1$ name = name.substring(2, name.length()-2); name = '\''+ name + '\''; } if (name.startsWith("'")) { //$NON-NLS-1$ builder.setLength(expressionStart); builder.append(name.toUpperCase()); } builder.insert(expressionStart, "(SELECT oid FROM pg_proc WHERE upper(proname) = "); //$NON-NLS-1$ builder.append(")"); //$NON-NLS-1$ } else { builder.insert(expressionStart, "cast("); //$NON-NLS-1$ builder.append(" AS ").append(type).append(")"); //$NON-NLS-1$ //$NON-NLS-2$ } } if (c != -1) { builder.append((char)c); } break; } } } break; } c = read(); break; } case '~': { if (rewrite) { int start = builder.length() - 1; boolean not = false; if (start > 0 && builder.charAt(start - 1) == '!') { not = true; start -= 1; } c = read(); boolean like = false; if (c == '~') { like = true; c = read(); } if (c == '*') { break; //can't handle } builder.setLength(start); if (not) { builder.append(" NOT"); //$NON-NLS-1$ } if (like) { builder.append(" LIKE "); //$NON-NLS-1$ } else { builder.append(" LIKE_REGEX "); //$NON-NLS-1$ } if (c != -1) { builder.append((char)c); } } c = read(); break; } default: { c = read(); } } } String result = builder.toString(); builder.setLength(0); if (result.length() == 0) { return null; } return result; } private void startRemark(boolean block) { blockRemark = block; insideRemark = true; } private void endRemark() { insideRemark = false; } private int read() throws IOException { int c = reader.read(); if (c != -1) { builder.append((char)c); } return c; } /** * 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; } public void setRewrite(boolean rewrite) { this.rewrite = rewrite; } }