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}