Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 52 additions & 38 deletions builder-api/src/main/java/org/acme/functions/LocationService.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,76 +11,90 @@
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

@Unremovable
@ApplicationScoped
public class LocationService {

private static final Logger LOG = Logger.getLogger(LocationService.class.getName());
private static final Set<String> ALLOWED_COLUMNS = Set.of(
"zipCode", "countyName", "countyFips", "stateAbbreviation"
);

@Inject
AgroalDataSource dataSource;

public Connection getDbConnection() throws SQLException {
Connection getDbConnection() throws SQLException {
return dataSource.getConnection();
}

/**
* Like {@link #lookup}, but matches filter values as case-insensitive prefix patterns.
* Trailing periods are stripped so abbreviations like "Mont." match "Montgomery".
*/
public static List<String> lookupFuzzy(String column, Map<String, Object> filters) {
Map<String, Object> patterns = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : filters.entrySet()) {
String raw = entry.getValue().toString().trim();
String pattern = raw.endsWith(".") ? raw.substring(0, raw.length() - 1) + "%" : raw + "%";
patterns.put(entry.getKey(), pattern);
}
return query(column, patterns, "LIKE");
}

public static List<String> lookup(String column, Map<String, Object> filters) {
return query(column, filters, "=");
}

private static List<String> query(String column, Map<String, Object> filters, String operator) {
if (!ALLOWED_COLUMNS.contains(column)) {
throw new IllegalArgumentException("Invalid column: " + column);
}
if (filters.isEmpty()) {
throw new IllegalArgumentException("filters must not be empty");
}
for (String key : filters.keySet()) {
if (!ALLOWED_COLUMNS.contains(key)) {
throw new IllegalArgumentException("Invalid filter key: " + key);
}
}

List<String> results = new ArrayList<>();
StringBuilder sql = new StringBuilder("SELECT DISTINCT ").append(column).append(" FROM locations WHERE ");

// construct WHERE clause; assume everything is ANDed together
StringBuilder sql = new StringBuilder("SELECT DISTINCT ").append(column).append(" FROM locations WHERE ");
boolean first = true;
for (String key : filters.keySet()) {
if (!first) {
sql.append(" AND ");
}
sql.append(key).append(" = ?");
if (!first) sql.append(" AND ");
sql.append(key).append(" ").append(operator).append(" ? COLLATE NOCASE");
first = false;
}

LocationService service = Arc.container().instance(LocationService.class).get();

Connection connection = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try (Connection connection = service.getDbConnection();
PreparedStatement pstmt = connection.prepareStatement(sql.toString())) {

try {
connection = service.getDbConnection();
pstmt = connection.prepareStatement(sql.toString());

// Set the values dynamically
int index = 1;
for (Object value : filters.values()) {
String stringValue = value.toString(); // db only has strings
pstmt.setString(index++, stringValue);
pstmt.setString(index++, value.toString().trim());
}

rs = pstmt.executeQuery();

while(rs.next()) {
String thisString = rs.getString(column);
results.add(thisString);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (rs != null) {
rs.close();
}
if (pstmt != null) {
pstmt.close();
}
if (connection != null) {
connection.close();
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
results.add(rs.getString(column));
}
} catch (SQLException e) {
e.printStackTrace();
}

} catch (SQLException e) {
LOG.severe("Location lookup failed for column=" + column + " filters=" + filters + ": " + e.getMessage());
throw new RuntimeException("Location lookup failed", e);
}

return results;

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,76 +10,90 @@
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

@Unremovable
@ApplicationScoped
public class LocationService {

private static final Logger LOG = Logger.getLogger(LocationService.class.getName());
private static final Set<String> ALLOWED_COLUMNS = Set.of(
"zipCode", "countyName", "countyFips", "stateAbbreviation"
);

@Inject
AgroalDataSource dataSource;

public Connection getDbConnection() throws SQLException {
Connection getDbConnection() throws SQLException {
return dataSource.getConnection();
}

/**
* Like {@link #lookup}, but matches filter values as case-insensitive prefix patterns.
* Trailing periods are stripped so abbreviations like "Mont." match "Montgomery".
*/
public static List<String> lookupFuzzy(String column, Map<String, Object> filters) {
Map<String, Object> patterns = new LinkedHashMap<>();
for (Map.Entry<String, Object> entry : filters.entrySet()) {
String raw = entry.getValue().toString().trim();
String pattern = raw.endsWith(".") ? raw.substring(0, raw.length() - 1) + "%" : raw + "%";
patterns.put(entry.getKey(), pattern);
}
return query(column, patterns, "LIKE");
}

public static List<String> lookup(String column, Map<String, Object> filters) {
return query(column, filters, "=");
}

private static List<String> query(String column, Map<String, Object> filters, String operator) {
if (!ALLOWED_COLUMNS.contains(column)) {
throw new IllegalArgumentException("Invalid column: " + column);
}
if (filters.isEmpty()) {
throw new IllegalArgumentException("filters must not be empty");
}
for (String key : filters.keySet()) {
if (!ALLOWED_COLUMNS.contains(key)) {
throw new IllegalArgumentException("Invalid filter key: " + key);
}
}

List<String> results = new ArrayList<>();
StringBuilder sql = new StringBuilder("SELECT DISTINCT ").append(column).append(" FROM locations WHERE ");

// construct WHERE clause; assume everything is ANDed together
StringBuilder sql = new StringBuilder("SELECT DISTINCT ").append(column).append(" FROM locations WHERE ");
boolean first = true;
for (String key : filters.keySet()) {
if (!first) {
sql.append(" AND ");
}
sql.append(key).append(" = ?");
if (!first) sql.append(" AND ");
sql.append(key).append(" ").append(operator).append(" ? COLLATE NOCASE");
first = false;
}

LocationService service = Arc.container().instance(LocationService.class).get();

Connection connection = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try (Connection connection = service.getDbConnection();
PreparedStatement pstmt = connection.prepareStatement(sql.toString())) {

try {
connection = service.getDbConnection();
pstmt = connection.prepareStatement(sql.toString());

// Set the values dynamically
int index = 1;
for (Object value : filters.values()) {
String stringValue = value.toString(); // db only has strings
pstmt.setString(index++, stringValue);
pstmt.setString(index++, value.toString().trim());
}

rs = pstmt.executeQuery();

while(rs.next()) {
String thisString = rs.getString(column);
results.add(thisString);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (rs != null) {
rs.close();
}
if (pstmt != null) {
pstmt.close();
}
if (connection != null) {
connection.close();
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
results.add(rs.getString(column));
}
} catch (SQLException e) {
e.printStackTrace();
}

} catch (SQLException e) {
LOG.severe("Location lookup failed for column=" + column + " filters=" + filters + ": " + e.getMessage());
throw new RuntimeException("Location lookup failed", e);
}

return results;

}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package org.codeforphilly.bdt.functions;

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;

@QuarkusTest
public class LocationServiceTest {

// --- lookup() ---

@Test
public void testLookupExactMatch() {
List<String> results = LocationService.lookup("countyName", Map.of("zipCode", "19107"));
assertFalse(results.isEmpty(), "Should find a county for zip 19107");
assertTrue(results.contains("Philadelphia"));
}

@Test
public void testLookupCaseInsensitiveFilterValue() {
List<String> uppercase = LocationService.lookup("zipCode", Map.of("stateAbbreviation", "PA", "countyName", "Philadelphia"));
List<String> lowercase = LocationService.lookup("zipCode", Map.of("stateAbbreviation", "pa", "countyName", "philadelphia"));
assertFalse(uppercase.isEmpty(), "Exact case should return results");
assertEquals(uppercase.size(), lowercase.size(),
"Lowercase filter values should return same number of results as title-case");
assertTrue(lowercase.containsAll(uppercase));
}

@Test
public void testLookupCaseInsensitiveMixedCase() {
List<String> titleCase = LocationService.lookup("zipCode", Map.of("stateAbbreviation", "PA", "countyName", "Philadelphia"));
List<String> upperCase = LocationService.lookup("zipCode", Map.of("stateAbbreviation", "PA", "countyName", "PHILADELPHIA"));
assertEquals(titleCase.size(), upperCase.size(),
"UPPERCASE filter value should return same results as title-case");
}

@Test
public void testLookupTrimsLeadingWhitespace() {
List<String> trimmed = LocationService.lookup("countyName", Map.of("zipCode", "19107"));
List<String> withSpace = LocationService.lookup("countyName", Map.of("zipCode", " 19107"));
assertEquals(trimmed, withSpace, "Leading whitespace on filter value should be trimmed");
}

@Test
public void testLookupTrimsTrailingWhitespace() {
List<String> trimmed = LocationService.lookup("countyName", Map.of("zipCode", "19107"));
List<String> withSpace = LocationService.lookup("countyName", Map.of("zipCode", "19107 "));
assertEquals(trimmed, withSpace, "Trailing whitespace on filter value should be trimmed");
}

@Test
public void testLookupReturnsEmptyForNoMatch() {
List<String> results = LocationService.lookup("countyName", Map.of("stateAbbreviation", "ZZ"));
assertTrue(results.isEmpty(), "Unknown state abbreviation should return an empty list");
}

// --- lookupFuzzy() ---

@Test
public void testLookupFuzzyAbbreviationWithPeriod() {
// "Mont." should match both "Montgomery" and "Montour" in PA
List<String> results = LocationService.lookupFuzzy("countyName",
Map.of("countyName", "Mont.", "stateAbbreviation", "PA"));
assertFalse(results.isEmpty(), "Abbreviated 'Mont.' should match counties starting with 'Mont'");
assertTrue(results.contains("Montgomery"),
"Expected 'Montgomery' in fuzzy results for 'Mont.'");
}

@Test
public void testLookupFuzzyPrefixNoTrailingPeriod() {
// "Los" should match "Los Angeles" and other "Los ..." counties
List<String> results = LocationService.lookupFuzzy("countyName",
Map.of("countyName", "Los"));
assertFalse(results.isEmpty(), "Prefix 'Los' should match counties starting with 'Los'");
assertTrue(results.stream().allMatch(name -> name.toLowerCase().startsWith("los")),
"All results should start with 'Los'");
}

@Test
public void testLookupFuzzyIsCaseInsensitive() {
List<String> upper = LocationService.lookupFuzzy("countyName",
Map.of("countyName", "PHILA", "stateAbbreviation", "PA"));
List<String> lower = LocationService.lookupFuzzy("countyName",
Map.of("countyName", "phila", "stateAbbreviation", "PA"));
assertEquals(upper.size(), lower.size(),
"Fuzzy lookup should be case-insensitive");
assertTrue(lower.containsAll(upper));
}

@Test
public void testLookupFuzzyTrimsWhitespace() {
List<String> trimmed = LocationService.lookupFuzzy("countyName", Map.of("countyName", "Phila", "stateAbbreviation", "PA"));
List<String> withSpace = LocationService.lookupFuzzy("countyName", Map.of("countyName", " Phila ", "stateAbbreviation", "PA"));
assertEquals(trimmed, withSpace, "Fuzzy lookup should trim whitespace before matching");
}

@Test
public void testLookupFuzzyReturnsEmptyForNoMatch() {
List<String> results = LocationService.lookupFuzzy("countyName",
Map.of("countyName", "ZZZZZ"));
assertTrue(results.isEmpty(), "Fuzzy lookup should return empty list when nothing matches");
}
}
2 changes: 2 additions & 0 deletions library-api/src/test/resources/application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Use a random port so tests don't conflict with other running services
quarkus.http.test-port=0