diff -r 7e08730da258 -r 4a1864c3e867 java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLCommandNamed.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java/sql-dk/src/main/java/info/globalcode/sql/dk/SQLCommandNamed.java Mon Mar 04 20:15:24 2019 +0100 @@ -0,0 +1,155 @@ +/** + * 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 . + */ +package info.globalcode.sql.dk; + +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; + +/** + * Has named parameters. + * + * @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 parameters; + private List parametersUsed = new ArrayList<>(); + private StringBuilder updatedQuery; + private Pattern pattern; + private SQLCommandNumbered numbered; + + public SQLCommandNamed(String query, List 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 { + return getSQLCommandNumbered().prepareStatement(c); + } + + @Override + public void parametrize(PreparedStatement ps) throws SQLException { + getSQLCommandNumbered().parametrize(ps); + } + + private void prepare() 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 SQL command with named parameters converted to SQL command with numbered parameters + */ + public SQLCommandNumbered getSQLCommandNumbered() throws SQLException { + if (numbered == null) { + prepare(); + numbered = new SQLCommandNumbered(updatedQuery.toString(), parametersUsed); + } + + return numbered; + } + + /** + * 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() throws PatternSyntaxException { + StringBuilder patternString = new StringBuilder(); + + patternString.append(namePrefix); + patternString.append("(?"); + 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() { + 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 + "(?.+?)" + 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 getParameters() { + return parameters; + } +}