001/* 002 * Copyright 2004-2020 H2 Group. Multiple-Licensed under the MPL 2.0, 003 * and the EPL 1.0 (https://h2database.com/html/license.html). 004 * Initial Developer: H2 Group 005 */ 006 007/* 008NOTE: Modified by FBSQL Team team. 009NOTE: This source has minor modifications of original source of H2 Group please see source comments for more info) 010 011Please contact FBSQL Team by E-Mail fbsql.team@gmail.com 012or visit https://fbsql.github.io if you need additional 013information or have any questions. 014*/ 015package org.h2.util; 016 017import java.io.Closeable; 018import java.io.IOException; 019import java.io.Reader; 020import java.util.Arrays; 021 022/** 023 * This class can split SQL scripts to single SQL statements. 024 * Each SQL statement ends with the character ';', however it is ignored 025 * in comments and quotes. 026 */ 027public class ScriptReader implements Closeable { 028 029 /** 030 * The block size for I/O operations. 031 * 032 * NOTE: This static variable was added by FBSQL Team team 033 */ 034 private static final int IO_BUFFER_SIZE = 4096; 035 private final Reader reader; 036 private char[] buffer; 037 038 /** 039 * The position in the buffer of the next char to be read 040 */ 041 private int bufferPos; 042 043 /** 044 * The position in the buffer of the statement start 045 */ 046 private int bufferStart = -1; 047 048 /** 049 * The position in the buffer of the last available char 050 */ 051 private int bufferEnd; 052 053 /** 054 * True if we have read past the end of file 055 */ 056 private boolean endOfFile; 057 058 /** 059 * True if we are inside a comment 060 */ 061 private boolean insideRemark; 062 063 /** 064 * Only valid if insideRemark is true. True if we are inside a block 065 * comment, false if we are inside a line comment 066 */ 067 private boolean blockRemark; 068 069 /** 070 * True if comments should be skipped completely by this reader. 071 */ 072 private boolean skipRemarks; 073 074 /** 075 * The position in buffer of start of comment 076 */ 077 private int remarkStart; 078 079 /** 080 * Create a new SQL script reader from the given reader 081 * 082 * @param reader the reader 083 */ 084 public ScriptReader(Reader reader) { 085 this.reader = reader; 086 buffer = new char[IO_BUFFER_SIZE * 2]; // NOTE: This line was modified by FBSQL Team team 087 } 088 089// /** 090// * Close the underlying reader. 091// */ 092// @Override 093// public void close() { 094// try { 095// reader.close(); 096// } catch (IOException e) { 097// throw DbException.convertIOException(e, null); 098// } 099// } 100 101 /** 102 * Close the underlying reader. 103 * @throws IOException 104 * 105 * NOTE: This method was modified by FBSQL Team team (for original version see above) 106 */ 107 @Override 108 public void close() throws IOException { 109 reader.close(); 110 } 111 112// /** 113// * Read a statement from the reader. This method returns null if the end has 114// * been reached. 115// * 116// * @return the SQL statement or null 117// */ 118// public String readStatement() { 119// if (endOfFile) { 120// return null; 121// } 122// try { 123// return readStatementLoop(); 124// } catch (IOException e) { 125// throw DbException.convertIOException(e, null); 126// } 127// } 128 129 /** 130 * Read a statement from the reader. This method returns null if the end has 131 * been reached. 132 * 133 * @return the SQL statement or null 134 * @throws IOException 135 * 136 * NOTE: This method was modified by FBSQL Team team (for original version see above) 137 */ 138 public String readStatement() throws IOException { 139 if (endOfFile) { 140 return null; 141 } 142 return readStatementLoop(); 143 } 144 145 private String readStatementLoop() throws IOException { 146 bufferStart = bufferPos; 147 int c = read(); 148 while (true) { 149 if (c < 0) { 150 endOfFile = true; 151 if (bufferPos - 1 == bufferStart) { 152 return null; 153 } 154 break; 155 } else if (c == ';') { 156 break; 157 } 158 switch (c) { 159 case '$': { 160 c = read(); 161 if (c == '$' && (bufferPos - bufferStart < 3 || buffer[bufferPos - 3] <= ' ')) { 162 // dollar quoted string 163 while (true) { 164 c = read(); 165 if (c < 0) { 166 break; 167 } 168 if (c == '$') { 169 c = read(); 170 if (c < 0) { 171 break; 172 } 173 if (c == '$') { 174 break; 175 } 176 } 177 } 178 c = read(); 179 } 180 break; 181 } 182 case '\'': 183 while (true) { 184 c = read(); 185 if (c < 0) { 186 break; 187 } 188 if (c == '\'') { 189 break; 190 } 191 } 192 c = read(); 193 break; 194 case '"': 195 while (true) { 196 c = read(); 197 if (c < 0) { 198 break; 199 } 200 if (c == '\"') { 201 break; 202 } 203 } 204 c = read(); 205 break; 206 case '/': { 207 c = read(); 208 if (c == '*') { 209 // block comment 210 startRemark(true); 211 while (true) { 212 c = read(); 213 if (c < 0) { 214 break; 215 } 216 if (c == '*') { 217 c = read(); 218 if (c < 0) { 219 clearRemark(); 220 break; 221 } 222 if (c == '/') { 223 endRemark(); 224 break; 225 } 226 } 227 } 228 c = read(); 229 } else if (c == '/') { 230 // single line comment 231 startRemark(false); 232 while (true) { 233 c = read(); 234 if (c < 0) { 235 clearRemark(); 236 break; 237 } 238 if (c == '\r' || c == '\n') { 239 endRemark(); 240 break; 241 } 242 } 243 c = read(); 244 } 245 break; 246 } 247 case '-': { 248 c = read(); 249 if (c == '-') { 250 // single line comment 251 startRemark(false); 252 while (true) { 253 c = read(); 254 if (c < 0) { 255 clearRemark(); 256 break; 257 } 258 if (c == '\r' || c == '\n') { 259 endRemark(); 260 break; 261 } 262 } 263 c = read(); 264 } 265 break; 266 } 267 default: { 268 c = read(); 269 } 270 } 271 } 272 return new String(buffer, bufferStart, bufferPos - 1 - bufferStart); 273 } 274 275 private void startRemark(boolean block) { 276 blockRemark = block; 277 remarkStart = bufferPos - 2; 278 insideRemark = true; 279 } 280 281 private void endRemark() { 282 clearRemark(); 283 insideRemark = false; 284 } 285 286 private void clearRemark() { 287 if (skipRemarks) { 288 Arrays.fill(buffer, remarkStart, bufferPos, ' '); 289 } 290 } 291 292 private int read() throws IOException { 293 if (bufferPos >= bufferEnd) { 294 return readBuffer(); 295 } 296 return buffer[bufferPos++]; 297 } 298 299 private int readBuffer() throws IOException { 300 if (endOfFile) { 301 return -1; 302 } 303 int keep = bufferPos - bufferStart; 304 if (keep > 0) { 305 char[] src = buffer; 306 if (keep + IO_BUFFER_SIZE > src.length) { // NOTE: This line was modified by FBSQL Team team 307 // protect against NegativeArraySizeException 308 if (src.length >= Integer.MAX_VALUE / 2) { 309 throw new IOException("Error in parsing script, " + "statement size exceeds 1G, " + "first 80 characters of statement looks like: " + new String(buffer, bufferStart, 80)); 310 } 311 buffer = new char[src.length * 2]; 312 } 313 System.arraycopy(src, bufferStart, buffer, 0, keep); 314 } 315 remarkStart -= bufferStart; 316 bufferStart = 0; 317 bufferPos = keep; 318 int len = reader.read(buffer, keep, IO_BUFFER_SIZE); // NOTE: This line was modified by FBSQL Team team 319 if (len == -1) { 320 // ensure bufferPos > bufferEnd 321 bufferEnd = -1024; 322 endOfFile = true; 323 // ensure the right number of characters are read 324 // in case the input buffer is still used 325 bufferPos++; 326 return -1; 327 } 328 bufferEnd = keep + len; 329 return buffer[bufferPos++]; 330 } 331 332 /** 333 * Check if this is the last statement, and if the single line or block 334 * comment is not finished yet. 335 * 336 * @return true if the current position is inside a remark 337 */ 338 public boolean isInsideRemark() { 339 return insideRemark; 340 } 341 342 /** 343 * If currently inside a remark, this method tells if it is a block comment 344 * (true) or single line comment (false) 345 * 346 * @return true if inside a block comment 347 */ 348 public boolean isBlockRemark() { 349 return blockRemark; 350 } 351 352 /** 353 * If comments should be skipped completely by this reader. 354 * 355 * @param skipRemarks true if comments should be skipped 356 */ 357 public void setSkipRemarks(boolean skipRemarks) { 358 this.skipRemarks = skipRemarks; 359 } 360 361}