/**
* SQL-DK
* Copyright © 2013 František Kučera (frantovo.cz)
*
* 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 <http://www.gnu.org/licenses/>.
*/
package info.globalcode.sql.dk;
import static info.globalcode.sql.dk.Functions.notNull;
import static info.globalcode.sql.dk.Functions.findByName;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/**
*
* @author Ing. František Kučera (frantovo.cz)
*/
public class SQLCommandNamed extends SQLCommand {
private static final Logger log = Logger.getLogger(SQLCommandNamed.class.getName());
private String namePrefix;
private String nameSuffix;
private List<NamedParameter> parameters;
private List<NamedParameter> parametersUsed = new ArrayList<>();
private StringBuilder updatedQuery;
private Pattern pattern;
public SQLCommandNamed(String query, List<NamedParameter> parameters, String namePrefix, String nameSuffix) {
super(query);
this.updatedQuery = new StringBuilder(query.length());
this.parameters = parameters;
this.namePrefix = namePrefix;
this.nameSuffix = nameSuffix;
}
@Override
public PreparedStatement prepareStatement(Connection c) throws SQLException {
try {
buildPattern();
placeParametersAndUpdateQuery();
logPossiblyMissingParameters();
} catch (PatternSyntaxException e) {
throw new SQLException("Name prefix „" + namePrefix + "“ or suffix „" + nameSuffix + "“ contain a wrong regular expression. " + e.getLocalizedMessage(), e);
}
return c.prepareStatement(updatedQuery.toString());
}
@Override
public void parametrize(PreparedStatement ps) throws SQLException {
int i = 1;
for (Parameter p : notNull(parametersUsed)) {
ps.setObject(i++, p.getValue(), p.getType().getCode());
}
}
/**
* Builds a regexp pattern that matches all parameter names (with prefix/suffix) and which has
* one group: parameter name (without prefix/suffix)
*/
private void buildPattern() {
StringBuilder patternString = new StringBuilder();
patternString.append(namePrefix);
patternString.append("(?<paramName>");
for (int i = 0; i < parameters.size(); i++) {
patternString.append(Pattern.quote(parameters.get(i).getName()));
if (i < parameters.size() - 1) {
patternString.append("|");
}
}
patternString.append(")");
patternString.append(nameSuffix);
pattern = Pattern.compile(patternString.toString());
}
private void placeParametersAndUpdateQuery() throws SQLException {
final String originalQuery = getQuery();
Matcher m = pattern.matcher(originalQuery);
int lastPosition = 0;
while (m.find(lastPosition)) {
String name = m.group("paramName");
updatedQuery.append(originalQuery.substring(lastPosition, m.start()));
updatedQuery.append("?");
parametersUsed.add(findByName(parameters, name));
lastPosition = m.end();
}
updatedQuery.append(originalQuery.substring(lastPosition, originalQuery.length()));
for (NamedParameter definedParameter : parameters) {
if (findByName(parametersUsed, definedParameter.getName()) == null) {
/**
* User can have predefined set of parameters and use them with different SQL
* queries that use only subset of these parameters → just warning, not exception.
*/
log.log(Level.WARNING, "Parameter „{0}“ is defined but not used in the query: „{1}“", new Object[]{definedParameter.getName(), originalQuery});
}
}
}
private void logPossiblyMissingParameters() {
Pattern p = Pattern.compile(namePrefix + "(?<paramName>.+?)" + nameSuffix);
Matcher m = p.matcher(updatedQuery);
int lastPosition = 0;
while (m.find(lastPosition)) {
/**
* We have not parsed and understood the SQL query; the parameter-like looking string
* could be inside a literal part of the query → just warning, not exception.
*/
log.log(Level.WARNING, "Possibly missing parameter „{0}“ in the query: „{1}“", new Object[]{m.group("paramName"), getQuery()});
lastPosition = m.end();
}
}
@Override
public List<NamedParameter> getParameters() {
return parameters;
}
}