item service: change search to only search database
Add fulltext index to item.name and use in searches
This commit is contained in:
@@ -26,7 +26,6 @@ package net.runelite.http.service.item;
|
|||||||
|
|
||||||
import com.google.common.cache.Cache;
|
import com.google.common.cache.Cache;
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import java.io.IOException;
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -53,9 +52,6 @@ public class ItemController
|
|||||||
private static final Duration CACHE_DUATION = Duration.ofMinutes(30);
|
private static final Duration CACHE_DUATION = Duration.ofMinutes(30);
|
||||||
private static final String RUNELITE_CACHE = "RuneLite-Cache";
|
private static final String RUNELITE_CACHE = "RuneLite-Cache";
|
||||||
|
|
||||||
private final Cache<String, SearchResult> cachedSearches = CacheBuilder.newBuilder()
|
|
||||||
.maximumSize(1024L)
|
|
||||||
.build();
|
|
||||||
private final Cache<Integer, Integer> cachedEmpty = CacheBuilder.newBuilder()
|
private final Cache<Integer, Integer> cachedEmpty = CacheBuilder.newBuilder()
|
||||||
.maximumSize(1024L)
|
.maximumSize(1024L)
|
||||||
.build();
|
.build();
|
||||||
@@ -217,37 +213,14 @@ public class ItemController
|
|||||||
@RequestMapping("/search")
|
@RequestMapping("/search")
|
||||||
public SearchResult search(HttpServletResponse response, @RequestParam String query)
|
public SearchResult search(HttpServletResponse response, @RequestParam String query)
|
||||||
{
|
{
|
||||||
// rs api seems to require lowercase
|
List<ItemEntry> result = itemService.search(query);
|
||||||
query = query.toLowerCase();
|
|
||||||
|
|
||||||
SearchResult searchResult = cachedSearches.getIfPresent(query);
|
itemService.queueSearch(query);
|
||||||
if (searchResult != null)
|
|
||||||
{
|
|
||||||
response.setHeader(RUNELITE_CACHE, "HIT");
|
|
||||||
return searchResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
SearchResult searchResult = new SearchResult();
|
||||||
{
|
searchResult.setItems(result.stream()
|
||||||
RSSearch search = itemService.fetchRSSearch(query);
|
.map(ItemEntry::toItem)
|
||||||
|
.collect(Collectors.toList()));
|
||||||
searchResult = new SearchResult();
|
return searchResult;
|
||||||
List<Item> items = search.getItems().stream()
|
|
||||||
.map(rsi -> rsi.toItem())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
searchResult.setItems(items);
|
|
||||||
|
|
||||||
cachedSearches.put(query, searchResult);
|
|
||||||
|
|
||||||
itemService.batchInsertItems(search);
|
|
||||||
|
|
||||||
response.setHeader(RUNELITE_CACHE, "MISS");
|
|
||||||
return searchResult;
|
|
||||||
}
|
|
||||||
catch (IOException ex)
|
|
||||||
{
|
|
||||||
log.warn("error while searching items", ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ public class ItemService
|
|||||||
+ " `icon` blob,\n"
|
+ " `icon` blob,\n"
|
||||||
+ " `icon_large` blob,\n"
|
+ " `icon_large` blob,\n"
|
||||||
+ " `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n"
|
+ " `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n"
|
||||||
+ " PRIMARY KEY (`id`)\n"
|
+ " PRIMARY KEY (`id`),\n"
|
||||||
|
+ " FULLTEXT idx_name (name)\n"
|
||||||
+ ") ENGINE=InnoDB";
|
+ ") ENGINE=InnoDB";
|
||||||
|
|
||||||
private static final String CREATE_PRICES = "CREATE TABLE IF NOT EXISTS `prices` (\n"
|
private static final String CREATE_PRICES = "CREATE TABLE IF NOT EXISTS `prices` (\n"
|
||||||
@@ -80,10 +81,11 @@ public class ItemService
|
|||||||
private static final String CREATE_PRICES_FK = "ALTER TABLE `prices`\n"
|
private static final String CREATE_PRICES_FK = "ALTER TABLE `prices`\n"
|
||||||
+ " ADD CONSTRAINT `item` FOREIGN KEY (`item`) REFERENCES `items` (`id`);";
|
+ " ADD CONSTRAINT `item` FOREIGN KEY (`item`) REFERENCES `items` (`id`);";
|
||||||
|
|
||||||
private static final int MAX_PENDING_LOOKUPS = 512;
|
private static final int MAX_PENDING = 512;
|
||||||
|
|
||||||
private final Sql2o sql2o;
|
private final Sql2o sql2o;
|
||||||
private final ConcurrentLinkedQueue<Integer> pendingPriceLookups = new ConcurrentLinkedQueue<>();
|
private final ConcurrentLinkedQueue<Integer> pendingPriceLookups = new ConcurrentLinkedQueue<>();
|
||||||
|
private final ConcurrentLinkedQueue<String> pendingSearches = new ConcurrentLinkedQueue<>();
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public ItemService(@Qualifier("Runelite SQL2O") Sql2o sql2o)
|
public ItemService(@Qualifier("Runelite SQL2O") Sql2o sql2o)
|
||||||
@@ -142,6 +144,18 @@ public class ItemService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<ItemEntry> search(String search)
|
||||||
|
{
|
||||||
|
try (Connection con = sql2o.open())
|
||||||
|
{
|
||||||
|
return con.createQuery("select id, name, description, type, match (name) against (:search) as score from items "
|
||||||
|
+ "where match (name) against (:search) order by score desc limit 10")
|
||||||
|
.throwOnMappingFailure(false) // otherwise it tries to map 'score'
|
||||||
|
.addParameter("search", search)
|
||||||
|
.executeAndFetch(ItemEntry.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ItemEntry fetchItem(int itemId)
|
public ItemEntry fetchItem(int itemId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -274,6 +288,9 @@ public class ItemService
|
|||||||
|
|
||||||
public RSSearch fetchRSSearch(String query) throws IOException
|
public RSSearch fetchRSSearch(String query) throws IOException
|
||||||
{
|
{
|
||||||
|
// rs api seems to require lowercase
|
||||||
|
query = query.toLowerCase();
|
||||||
|
|
||||||
HttpUrl searchUrl = RS_SEARCH_URL
|
HttpUrl searchUrl = RS_SEARCH_URL
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.addQueryParameter("alpha", query)
|
.addQueryParameter("alpha", query)
|
||||||
@@ -286,7 +303,7 @@ public class ItemService
|
|||||||
return fetchJson(request, RSSearch.class);
|
return fetchJson(request, RSSearch.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void batchInsertItems(RSSearch search)
|
private void batchInsertItems(RSSearch search)
|
||||||
{
|
{
|
||||||
try (Connection con = sql2o.beginTransaction())
|
try (Connection con = sql2o.beginTransaction())
|
||||||
{
|
{
|
||||||
@@ -347,7 +364,7 @@ public class ItemService
|
|||||||
|
|
||||||
public void queueLookup(int itemId)
|
public void queueLookup(int itemId)
|
||||||
{
|
{
|
||||||
if (pendingPriceLookups.size() >= MAX_PENDING_LOOKUPS)
|
if (pendingPriceLookups.size() >= MAX_PENDING)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -355,6 +372,16 @@ public class ItemService
|
|||||||
pendingPriceLookups.add(itemId);
|
pendingPriceLookups.add(itemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void queueSearch(String search)
|
||||||
|
{
|
||||||
|
if (pendingSearches.size() >= MAX_PENDING)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingSearches.add(search);
|
||||||
|
}
|
||||||
|
|
||||||
@Scheduled(fixedDelay = 5000)
|
@Scheduled(fixedDelay = 5000)
|
||||||
public void checkPrices()
|
public void checkPrices()
|
||||||
{
|
{
|
||||||
@@ -366,4 +393,25 @@ public class ItemService
|
|||||||
|
|
||||||
fetchPrice(itemId);
|
fetchPrice(itemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Scheduled(fixedDelay = 5000)
|
||||||
|
public void checkSearches()
|
||||||
|
{
|
||||||
|
String search = pendingSearches.poll();
|
||||||
|
if (search == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
RSSearch reSearch = fetchRSSearch(search);
|
||||||
|
|
||||||
|
batchInsertItems(reSearch);
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
log.warn("error while searching items", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user