lib/search/word_search: add word count search

This commit is contained in:
2025-05-16 23:50:01 +02:00
parent 080638e7ef
commit b6661c734f
4 changed files with 127 additions and 31 deletions

View File

@@ -27,10 +27,16 @@ class QueryWord extends Command {
libsqlitePath: argResults!.option('libsqlite')!,
);
final String searchWord = 'かな';
final time = Stopwatch()..start();
final result = await JaDBConnection(db).searchWord('かな');
final count = await JaDBConnection(db).searchWordCount(searchWord);
time.stop();
final time2 = Stopwatch()..start();
final result = await JaDBConnection(db).searchWord(searchWord);
time2.stop();
if (result == null) {
print("Invalid search");
} else if (result.isEmpty) {
@@ -42,6 +48,8 @@ class QueryWord extends Command {
}
}
print("Query took ${time.elapsedMilliseconds}ms");
print("Total count: ${count}");
print("Count query took ${time.elapsedMilliseconds}ms");
print("Query took ${time2.elapsedMilliseconds}ms");
}
}

View File

@@ -12,14 +12,18 @@ class JaDBConnection {
const JaDBConnection(this._connection);
Future<KanjiSearchResult?> searchKanji(String kanji) async =>
Future<KanjiSearchResult?> searchKanji(String kanji) =>
searchKanjiWithDbConnection(this._connection, kanji);
Future<RadicalsSearchResult> searchKanjiByRadicals(
List<String> radicals) async {
List<String> radicals,
) async {
throw UnimplementedError();
}
Future<List<WordSearchResult>?> searchWord(String word) async =>
Future<List<WordSearchResult>?> searchWord(String word) =>
searchWordWithDbConnection(this._connection, word);
Future<int?> searchWordCount(String word) =>
searchWordCountWithDbConnection(this._connection, word);
}

View File

@@ -30,14 +30,15 @@ SearchMode _determineSearchMode(String word) {
(String, List<Object?>) _kanjiReadingTemplate(
String tableName,
String word,
int pageSize,
) =>
String word, {
int pageSize = 10,
bool countOnly = false,
}) =>
(
'''
WITH
fts_results AS (
SELECT
SELECT DISTINCT
"${tableName}FTS"."entryId",
100
+ "${tableName}"."baseScore"
@@ -49,34 +50,39 @@ SearchMode _determineSearchMode(String word) {
JOIN "${tableName}" USING ("entryId", "reading")
WHERE "${tableName}FTS"."reading" MATCH ? || '*'
ORDER BY "score" DESC
LIMIT ?
${!countOnly ? 'LIMIT ?' : ''}
),
non_fts_results AS (
SELECT DISTINCT
"entryId",
50
+ "${tableName}"."baseScore"
- (substr(COALESCE("${TanosJLPTTableNames.jlptTag}"."jlptLevel", 'N0'), 2) * -5)
AS "score"
FROM "${tableName}"
LEFT JOIN "${TanosJLPTTableNames.jlptTag}" USING ("entryId")
WHERE "reading" LIKE '%' || ? || '%'
AND "entryId" NOT IN (SELECT "entryId" FROM "fts_results")
ORDER BY
"score" DESC,
"entryId" ASC
${!countOnly ? 'LIMIT ?' : ''}
)
SELECT *
FROM "fts_results"
UNION ALL
SELECT
"entryId",
50
+ "${tableName}"."baseScore"
- (substr(COALESCE("${TanosJLPTTableNames.jlptTag}"."jlptLevel", 'N0'), 2) * -5)
AS "score"
FROM "${tableName}"
LEFT JOIN "${TanosJLPTTableNames.jlptTag}" USING ("entryId")
WHERE "reading" LIKE '%' || ? || '%'
AND "entryId" NOT IN (SELECT "entryId" FROM "fts_results")
ORDER BY
"score" DESC,
"entryId" ASC
LIMIT ?
${countOnly ? 'SELECT COUNT("entryId") AS count' : 'SELECT "entryId", "score"'}
FROM (
SELECT * FROM fts_results
UNION ALL
SELECT * FROM non_fts_results
)
'''
.trim(),
[
word,
word,
pageSize,
if (!countOnly) pageSize,
word,
pageSize,
if (!countOnly) pageSize,
]
);
@@ -102,7 +108,7 @@ Future<List<ScoredEntryId>> fetchEntryIds(
final (query, args) = _kanjiReadingTemplate(
JMdictTableNames.kanjiElement,
word,
pageSize,
pageSize: pageSize,
);
entryIds = (await connection.rawQuery(query, args))
.map((row) => ScoredEntryId(
@@ -116,7 +122,7 @@ Future<List<ScoredEntryId>> fetchEntryIds(
final (query, args) = _kanjiReadingTemplate(
JMdictTableNames.readingElement,
word,
pageSize,
pageSize: pageSize,
);
entryIds = (await connection.rawQuery(query, args))
.map((row) => ScoredEntryId(
@@ -153,3 +159,63 @@ Future<List<ScoredEntryId>> fetchEntryIds(
return entryIds;
}
Future<int?> fetchEntryIdCount(
DatabaseExecutor connection,
String word,
SearchMode searchMode,
) async {
if (searchMode == SearchMode.Auto) {
searchMode = _determineSearchMode(word);
}
assert(
word.isNotEmpty,
'Word should not be empty when fetching entry IDs',
);
late final int? entryIdCount;
switch (searchMode) {
case SearchMode.Kanji:
final (query, args) = _kanjiReadingTemplate(
JMdictTableNames.kanjiElement,
word,
pageSize: 1,
countOnly: true,
);
entryIdCount = (await connection.rawQuery(query, args))
.firstOrNull?['count'] as int?;
break;
case SearchMode.Kana:
final (query, args) = _kanjiReadingTemplate(
JMdictTableNames.readingElement,
word,
pageSize: 1,
countOnly: true,
);
entryIdCount = (await connection.rawQuery(query, args))
.firstOrNull?['count'] as int?;
break;
case SearchMode.English:
entryIdCount = (await connection.query(
JMdictTableNames.senseGlossary,
columns: ['COUNT(DISTINCT entryId)'],
where: 'english LIKE ?',
whereArgs: ['%$word%'],
))
.firstOrNull?['COUNT(DISTINCT entryId)'] as int?;
break;
case SearchMode.MixedKana:
case SearchMode.MixedKanji:
default:
throw UnimplementedError(
'Search mode $searchMode is not implemented',
);
}
return entryIdCount;
}

View File

@@ -77,3 +77,21 @@ Future<List<WordSearchResult>?> searchWordWithDbConnection(
return result;
}
Future<int?> searchWordCountWithDbConnection(
DatabaseExecutor connection,
String word, {
SearchMode searchMode = SearchMode.Auto,
}) async {
if (word.isEmpty) {
return null;
}
final int? entryIdCount = await fetchEntryIdCount(
connection,
word,
searchMode,
);
return entryIdCount;
}