
org.nuiton.topia.persistence.script.SqlScriptReader Maven / Gradle / Ivy
Show all versions of topia-extension Show documentation
package org.nuiton.topia.persistence.script;
/*-
* #%L
* ObServe Toolkit :: ToPIA Extension
* %%
* Copyright (C) 2017 - 2018 IRD, Ultreia.io
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* .
* #L%
*/
import io.ultreia.java4all.util.SingletonSupplier;
import org.apache.commons.lang3.mutable.MutableLong;
import org.nuiton.topia.persistence.TopiaException;
import org.nuiton.util.GZUtil;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.zip.GZIPInputStream;
/**
* Create an iterable on a SQL text content. The content is iterated on each SQL
* statement. For information the class handles semi-colon in quote.
*
* File example:
* INSERT INTO client (prenom, age) VALUES ('John', 11);
* INSERT INTO client (prenom, age) VALUES ('Jack', 12);
*
* Then:
* SqlScriptReader source = new SqlScriptReader(stream);
* for (String sql : source) {
* // process sql variable
* }
* Created by tchemit on 10/05/2018.
*
* @author Tony Chemit - [email protected]
* @author jruchaud
*/
@SuppressWarnings("WeakerAccess")
public class SqlScriptReader implements Iterable, Closeable {
private final SingletonSupplier source;
private final boolean skipComment;
private final boolean skipTrim;
private final MutableLong statementCount = new MutableLong();
private final boolean gzip;
private SqlScriptReader(SingletonSupplier source, boolean skipComment, boolean skipTrim, boolean gzip) {
this.source = source;
this.skipComment = skipComment;
this.skipTrim = skipTrim;
this.gzip = gzip;
}
public static SqlScriptReader of(String source) {
return builder(Objects.requireNonNull(source)).build();
}
public static SqlScriptReader of(byte[] source) {
return builder(Objects.requireNonNull(source)).build();
}
public static SqlScriptReader of(Path source) {
return builder(Objects.requireNonNull(source)).build();
}
public static SqlScriptReader of(URL source) {
return builder(Objects.requireNonNull(source)).build();
}
public static SqlScriptReader of(InputStream source) {
return builder(source).build();
}
public static Builder builder(String source) {
return builder(Objects.requireNonNull(source).getBytes());
}
public static Builder builder(InputStream content) {
return new Builder(() -> new BufferedInputStream(Objects.requireNonNull(content)));
}
public static Builder builder(byte[] source) {
return new Builder(() -> new BufferedInputStream(new ByteArrayInputStream(Objects.requireNonNull(source))));
}
public static Builder builder(Path source) {
return new Builder(() -> {
try {
return new BufferedInputStream(Files.newInputStream(Objects.requireNonNull(source)));
} catch (IOException e) {
throw new IllegalArgumentException("Can't get input stream from source: " + source, e);
}
});
}
public static Builder builder(URL source) {
return new Builder(() -> {
try {
return new BufferedInputStream(Objects.requireNonNull(source).openStream());
} catch (IOException e) {
throw new IllegalArgumentException("Can't get input stream from source: " + source, e);
}
});
}
public static Builder builder(SqlScriptReader location) {
return new Builder(location.getSource(), location.isGzip());
}
@SuppressWarnings("NullableProblems")
@Override
public Iterator iterator() {
return skipComment || skipTrim ? new SqlFileReaderIteratorWithoutComment(source, statementCount, skipComment, skipTrim) : new SqlFileReaderIterator(source, statementCount);
}
public long getStatementCount() {
return statementCount.getValue();
}
public boolean isGzip() {
return gzip;
}
public SingletonSupplier getSource() {
return source;
}
@Override
public void close() throws IOException {
if (source.withValue()) {
source.get().close();
}
}
enum SqlFileParserState {NORMAL, QUOTE, COMMENT}
public static class Builder {
private final boolean gzip;
private final SingletonSupplier sourceReader;
private final Supplier source;
private Charset encoding = StandardCharsets.UTF_8;
private boolean skipComment = true;
private boolean skipTrim = true;
public Builder(Supplier source) {
this.source = Objects.requireNonNull(source);
this.sourceReader = null;
try {
this.gzip = GZUtil.isGzipStream(source.get());
} catch (IOException e) {
throw new IllegalArgumentException("Can't determine if input stream is gzip", e);
}
}
public Builder(SingletonSupplier source, boolean gzip) {
this.source = null;
this.sourceReader = Objects.requireNonNull(source);
this.gzip = gzip;
}
public Builder keepCommentLine() {
skipComment = false;
return this;
}
public Builder keepEmptyLine() {
skipTrim = false;
return this;
}
public Builder encoding(Charset encoding) {
this.encoding = Objects.requireNonNull(encoding);
return this;
}
public SqlScriptReader build() {
if (source != null) {
InputStream stream = source.get();
try {
if (gzip) {
stream = new GZIPInputStream(this.source.get());
}
} catch (IOException e) {
throw new IllegalStateException("Can't get gzip input stream", e);
}
InputStream finalStream = stream;
return new SqlScriptReader(SingletonSupplier.of(() -> new BufferedReader(new InputStreamReader(finalStream, encoding))), skipComment, skipTrim, gzip);
}
return new SqlScriptReader(sourceReader, skipComment, skipTrim, gzip);
}
}
/**
* Use to create an iterator on the iterable.
*/
public static class SqlFileReaderIterator implements Iterator {
private final SingletonSupplier source;
private final StringBuilder buffer;
private final MutableLong statementCount;
/** The variable is used to keep if the iterator reach the end */
private int scanner;
private SqlFileReaderIterator(SingletonSupplier source, MutableLong statementCount) {
this.source = source;
this.statementCount = statementCount;
this.buffer = new StringBuilder();
}
@Override
public boolean hasNext() {
return scanner != -1;
}
@Override
public String next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
SqlFileParserState state = SqlFileParserState.NORMAL;
buffer.setLength(0);
try {
while ((scanner = source.get().read()) != -1) {
char character = (char) scanner;
switch (state) {
case NORMAL:
switch (character) {
// Remove useless character
case '\n':
case '\r':
break;
// Search the end of query
case ';':
buffer.append(";");
return returnStatement();
// Enter comment state if you have --
case '-':
int length = buffer.length();
if (length != 0 && buffer.charAt(length - 1) == '-') {
state = SqlFileParserState.COMMENT;
}
buffer.append(character);
break;
// Enter quote state
case '\'':
state = SqlFileParserState.QUOTE;
buffer.append(character);
break;
// By default append character
default:
buffer.append(character);
break;
}
break;
case QUOTE:
// Remove useless character
switch (character) {
// Search the end of quote
case '\'':
state = SqlFileParserState.NORMAL;
buffer.append(character);
break;
// By default append character
default:
buffer.append(character);
break;
}
break;
case COMMENT:
switch (character) {
// Search the end of comment
case '\n':
case '\r':
return returnStatement();
// By default append character
default:
buffer.append(character);
break;
}
break;
}
}
} catch (IOException ex) {
throw new TopiaException(ex);
}
return returnStatement();
}
private String returnStatement() {
statementCount.increment();
return buffer.toString();
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove");
}
}
public static class SqlFileReaderIteratorWithoutComment implements Iterator {
private final SqlFileReaderIterator delegate;
private final MutableLong statementCount;
private final boolean skipComment;
private final boolean skipTrim;
private String next;
private SqlFileReaderIteratorWithoutComment(SingletonSupplier source, MutableLong statementCount, boolean skipComment, boolean skipTrim) {
this.delegate = new SqlFileReaderIterator(source, new MutableLong());
this.statementCount = statementCount;
this.skipComment = skipComment;
this.skipTrim = skipTrim;
}
@Override
public boolean hasNext() {
if (next != null) {
return true;
}
boolean hasNext = delegate.hasNext();
if (hasNext) {
next = delegate.next();
if (skipTrim && next.trim().isEmpty()) {
next = null;
return hasNext();
}
if (skipComment && next.trim().startsWith("--")) {
next = null;
return hasNext();
}
}
return hasNext;
}
@Override
public String next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
statementCount.increment();
String next = this.next;
this.next = null;
return next;
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove");
}
}
}