Skip to content

Commit

Permalink
Review and simplify DFunction and corresponding unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
spannm committed Mar 24, 2024
1 parent efc1b98 commit 381b38b
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 71 deletions.
103 changes: 52 additions & 51 deletions src/main/java/net/ucanaccess/converters/DFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@
import java.util.regex.Pattern;

public class DFunction {
private static final Pattern PAT_FROM = Pattern.compile("\\w*FROM\\w*", Pattern.CASE_INSENSITIVE);
private static final String SELECT_FROM = "(?i)SELECT(.*\\W)(?i)FROM(.*)";
private static final String DFUNCTIONS_WHERE = "(?i)_\\s*\\(\\s*['\"](.*)['\"]\\,\\s*['\"](.*)['\"]\\,\\s*['\"](.*)['\"]\\s*\\)";
private static final String DFUNCTIONS_WHERE_DYNAMIC = "(?i)_\\s*\\(\\s*['\"](.*)['\"]\\,\\s*['\"](.*)['\"]\\,(.*)\\)";
private static final String DFUNCTIONS_NO_WHERE = "(?i)_\\s*\\(\\s*['\"](.*)['\"]\\,\\s*['\"](.*)['\"]\\s*\\)";
private static final String IDENTIFIER = "(\\W)((?i)_)(\\W)";
private static final List<String> DFUNCTIONLIST = List.of("COUNT", "MAX", "MIN", "SUM", "AVG", "LAST", "FIRST", "LOOKUP");
private static final Pattern PAT_SELECT_FROM = Pattern.compile("SELECT(.*\\W)FROM(.*)", Pattern.CASE_INSENSITIVE);
private static final String DFUNCTIONS_WHERE = "\\s*\\(\\s*['\"](.*)['\"]\\,\\s*['\"](.*)['\"]\\,\\s*['\"](.*)['\"]\\s*\\)";
private static final String DFUNCTIONS_WHERE_DYNAMIC = "\\s*\\(\\s*['\"](.*)['\"]\\,\\s*['\"](.*)['\"]\\,(.*)\\)";
private static final String DFUNCTIONS_NO_WHERE = "\\s*\\(\\s*['\"](.*)['\"]\\,\\s*['\"](.*)['\"]\\s*\\)";
private static final List<String> DFUNCTION_LIST = List.of("COUNT", "MAX", "MIN", "SUM", "AVG", "LAST", "FIRST", "LOOKUP");

private Connection conn;
private final String sql;
Expand All @@ -29,42 +27,43 @@ public DFunction(Connection _conn, String _sql) {
}

private String convertDFunctions() {
String sql0 = sql;
try {
boolean hasFrom = PAT_FROM.matcher(sql).find();
String init = hasFrom ? " (SELECT " : "";
String end = hasFrom ? " ) " : "";
for (String s : DFUNCTIONLIST) {

String fun = "D" + s;
s = "lookup".equalsIgnoreCase(s) ? " " : s;
sql0 = sql0.replaceAll(DFUNCTIONS_WHERE.replaceFirst("_", fun),
init + s + "($1) FROM $2 WHERE $3 " + end);
sql0 = sql0.replaceAll(DFUNCTIONS_NO_WHERE.replaceFirst("_", fun), init + s + "($1) FROM $2 " + end);
Pattern patDfd = Pattern.compile(DFUNCTIONS_WHERE_DYNAMIC.replaceFirst("_", fun));
for (Matcher mtc = patDfd.matcher(sql0); mtc.find(); mtc = patDfd.matcher(sql0)) {
StringBuilder sb = new StringBuilder();
String g3 = mtc.group(3);
String tableN = mtc.group(2).trim();
String alias = tableN.startsWith("[") && tableN.endsWith("]") ? "[" + unpad(tableN) + "_DALIAS]"
: tableN + "_DALIAS";
String tn = tableN.startsWith("[") && tableN.endsWith("]") ? unpad(tableN) : tableN;
sb.append(init).append(s).append("(").append(mtc.group(1)).append(") FROM ").append(tableN)
.append(" AS ").append(alias).append(" WHERE ");
boolean accessConcat = g3.indexOf('&') > 0;
boolean sqlConcat = g3.indexOf("||") > 0;
if (accessConcat || sqlConcat) {
String concat = accessConcat ? "&" : Pattern.quote("||");
String[] pts = g3.split(concat, -1);
for (String tkn : pts) {
if (isQuoted(tkn)) {
tkn = tkn.trim();
sb.append(unpad(tkn));
} else {
tkn += " ";
String sqlOut = sql;
boolean hasFrom = sql.toUpperCase().contains(" FROM ");

String init = hasFrom ? " (SELECT " : "";
String end = hasFrom ? " ) " : "";
for (String f : DFUNCTION_LIST) {

String dfun = "D" + f;
if ("lookup".equalsIgnoreCase(f)) {
f = " ";
}
sqlOut = Pattern.compile(dfun + DFUNCTIONS_WHERE, Pattern.CASE_INSENSITIVE).matcher(sqlOut).replaceAll(init + f + "($1) FROM $2 WHERE $3" + end);
sqlOut = Pattern.compile(dfun + DFUNCTIONS_NO_WHERE, Pattern.CASE_INSENSITIVE).matcher(sqlOut).replaceAll(init + f + "($1) FROM $2" + end);

Pattern patDfd = Pattern.compile(dfun + DFUNCTIONS_WHERE_DYNAMIC, Pattern.CASE_INSENSITIVE);
for (Matcher mtc = patDfd.matcher(sqlOut); mtc.find(); mtc = patDfd.matcher(sqlOut)) {
StringBuilder sb = new StringBuilder();
String g3 = mtc.group(3);
String tableN = mtc.group(2).trim();
String alias = tableN.startsWith("[") && tableN.endsWith("]") ? "[" + unpad(tableN) + "_DALIAS]" : tableN + "_DALIAS";
String tn = tableN.startsWith("[") && tableN.endsWith("]") ? unpad(tableN) : tableN;
sb.append(init).append(f).append("(").append(mtc.group(1)).append(") FROM ").append(tableN)
.append(" AS ").append(alias).append(" WHERE ");
boolean accessConcat = g3.indexOf('&') > 0;
boolean sqlConcat = g3.indexOf("||") > 0;
if (accessConcat || sqlConcat) {
String concat = accessConcat ? "&" : Pattern.quote("||");
String[] pts = g3.split(concat, -1);
for (String tkn : pts) {
if (isQuoted(tkn)) {
tkn = tkn.trim();
sb.append(unpad(tkn));
} else {
tkn += " ";
try {
for (String cln : getColumnNames(tn.toUpperCase())) {
String oppn = IDENTIFIER.replaceFirst("_", cln);
Pattern patOppn = Pattern.compile(oppn);
Pattern patOppn = Pattern.compile("(\\W)(" + cln + ")(\\W)", Pattern.CASE_INSENSITIVE);
Matcher mtcop = patOppn.matcher(tkn);
if (!mtcop.find()) {
continue;
Expand All @@ -74,27 +73,29 @@ private String convertDFunctions() {
&& tkn.charAt(mtcop.start(1) - 1) == '.') {
continue;
}
tkn = tkn.replaceAll(oppn,
tkn = tkn.replaceAll("(\\W)((?i)" + cln + ")(\\W)",
"[".equals(pref) ? resolveAmbiguosTableName(cln) + ".$1$2$3"
: "$1" + resolveAmbiguosTableName(cln) + ".$2$3");
}
sb.append(tkn);
} catch (SQLException _ignored) {
}
sb.append(tkn);
}
}
sb.append(end);
sql0 = sql0.replaceFirst(DFUNCTIONS_WHERE_DYNAMIC.replaceFirst("_", fun), sb.toString());
}
sb.append(end);
sqlOut = sqlOut.replaceFirst(DFUNCTIONS_WHERE_DYNAMIC.replaceFirst("_", dfun), sb.toString());
}
} catch (SQLException _ignored) {
}
return sql0;

return sqlOut;
}

private String resolveAmbiguosTableName(String _identifier) {
return Try.withResources(conn::createStatement, st -> {
String f4t = SQLConverter.convertSQL(
sql.replaceAll("[\r\n]", " ").replaceFirst(SELECT_FROM, "SELECT " + _identifier + " FROM $2 ")).getSql();
String sqlOut = sql.replaceAll("[\\r\\n]+", " ");
sqlOut = PAT_SELECT_FROM.matcher(sqlOut).replaceFirst("SELECT " + _identifier + " FROM $2");
String f4t = SQLConverter.convertSQL(sqlOut).getSql();
ResultSetMetaData rsmd = st.executeQuery(f4t).getMetaData();
String tableN = rsmd.getTableName(1);
return tableN == null || tableN.isBlank() ? _identifier : tableN;
Expand All @@ -121,7 +122,7 @@ private List<String> getColumnNames(String tableName) throws SQLException {
private static boolean isQuoted(String g3) {
g3 = g3.trim();
return g3.startsWith("'") && g3.endsWith("'") && g3.substring(1, g3.length() - 1).indexOf('\'') < 0
|| g3.startsWith("\"") && g3.endsWith("\"") && g3.substring(1, g3.length() - 1).indexOf('"') < 0;
|| g3.startsWith("\"") && g3.endsWith("\"") && g3.substring(1, g3.length() - 1).indexOf('"') < 0;
}

private static String unpad(String tkn) {
Expand Down
25 changes: 5 additions & 20 deletions src/test/java/net/ucanaccess/jdbc/AggregateFunctionsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,20 @@
import net.ucanaccess.test.AccessVersionSource;
import net.ucanaccess.test.UcanaccessBaseTest;
import net.ucanaccess.type.AccessVersion;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;

import java.sql.SQLException;
import java.util.Locale;
import java.util.Objects;

class AggregateFunctionsTest extends UcanaccessBaseTest {

@BeforeAll
static void setLocale() {
locale = Locale.getDefault();
Locale.setDefault(Locale.US);
}

@AfterAll
static void resetLocale() {
Locale.setDefault(Objects.requireNonNullElseGet(locale, Locale::getDefault));
}

@Override
protected void init(AccessVersion _accessVersion) throws SQLException {
super.init(_accessVersion);

executeStatements("CREATE TABLE t_aggrfunc (id INTEGER, descr TEXT(400), num NUMERIC(12,3), date0 DATETIME)",
"INSERT INTO t_aggrfunc (id, descr, num, date0) VALUES(1234, 'Show must go off',-1110.55446,#11/22/2003 10:42:58 PM#)",
"INSERT INTO t_aggrfunc (id, descr, num, date0) VALUES(12344, 'Show must go up and down',-113.55446,#11/22/2006 10:42:58 PM#)");
executeStatements(
"CREATE TABLE t_aggrfunc (id INTEGER, descr TEXT(400), num NUMERIC(12,3), date0 DATETIME)",
"INSERT INTO t_aggrfunc (id, descr, num, date0) VALUES(1234, 'Show must go off', -1110.55446, #11/22/2003 10:42:58 PM#)",
"INSERT INTO t_aggrfunc (id, descr, num, date0) VALUES(12344, 'Show must go up and down', -113.55446, #11/22/2006 10:42:58 PM#)");
}

@ParameterizedTest(name = "[{index}] {0}")
Expand All @@ -39,8 +25,7 @@ void testDCount(AccessVersion _accessVersion) throws SQLException {
init(_accessVersion);

checkQuery("SELECT id, DCount('*', 't_aggrfunc', '1=1') FROM [t_aggrfunc]", recs(rec(1234, 2), rec(12344, 2)));
checkQuery("SELECT id AS [WW \"SS], DCount('descr', 't_aggrfunc', '1=1') FROM t_aggrfunc",
recs(rec(1234, 2), rec(12344, 2)));
checkQuery("SELECT id AS [WW \"SS], DCount('descr', 't_aggrfunc', '1=1') FROM t_aggrfunc", recs(rec(1234, 2), rec(12344, 2)));
checkQuery("SELECT DCount('*', 't_aggrfunc', '1=1') ", singleRec(2));
}

Expand Down

0 comments on commit 381b38b

Please sign in to comment.