Remove fallback to like
This commit is contained in:
204
lib/db.dart
204
lib/db.dart
@@ -85,87 +85,6 @@ CREATE TRIGGER IF NOT EXISTS notes_au AFTER UPDATE ON notes BEGIN
|
|||||||
END;
|
END;
|
||||||
''';
|
''';
|
||||||
|
|
||||||
static Future<String?> _getIcuExtensionPath() async {
|
|
||||||
if (!Platform.isWindows) {
|
|
||||||
debugPrint('Not on Windows, skipping ICU');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Get the executable's directory
|
|
||||||
final exePath = Platform.resolvedExecutable;
|
|
||||||
final exeDir = path.dirname(exePath);
|
|
||||||
final currentDir = Directory.current.path;
|
|
||||||
|
|
||||||
debugPrint('\n================== ICU EXTENSION SETUP ==================');
|
|
||||||
debugPrint('Current working directory: $currentDir');
|
|
||||||
debugPrint('Executable path: $exePath');
|
|
||||||
debugPrint('Executable directory: $exeDir');
|
|
||||||
|
|
||||||
// In release mode, we want the DLL next to the exe
|
|
||||||
final targetPath = path.join(exeDir, 'sqlite3_icu.dll');
|
|
||||||
final targetFile = File(targetPath);
|
|
||||||
|
|
||||||
// If the DLL doesn't exist in the target location, copy it from assets
|
|
||||||
if (!await targetFile.exists()) {
|
|
||||||
debugPrint(
|
|
||||||
'DLL not found at target location, attempting to copy from assets...',
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
// Load from assets and write to target location
|
|
||||||
final assetPath = 'assets/windows/sqlite3_icu.dll';
|
|
||||||
debugPrint('Trying to load from asset path: $assetPath');
|
|
||||||
|
|
||||||
final data = await rootBundle.load(assetPath);
|
|
||||||
debugPrint(
|
|
||||||
'Asset loaded successfully, size: ${data.lengthInBytes} bytes',
|
|
||||||
);
|
|
||||||
|
|
||||||
debugPrint('Writing to target path: $targetPath');
|
|
||||||
await targetFile.writeAsBytes(
|
|
||||||
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes),
|
|
||||||
);
|
|
||||||
debugPrint('Successfully copied DLL to: $targetPath');
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
debugPrint('Failed to copy DLL from assets:');
|
|
||||||
debugPrint('Error: $e');
|
|
||||||
debugPrint('Stack trace: $stackTrace');
|
|
||||||
|
|
||||||
// Try to list available assets for debugging
|
|
||||||
try {
|
|
||||||
final manifestContent = await rootBundle.loadString(
|
|
||||||
'AssetManifest.json',
|
|
||||||
);
|
|
||||||
debugPrint('Available assets in manifest:');
|
|
||||||
debugPrint(manifestContent);
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint('Could not load asset manifest: $e');
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
debugPrint('Found existing DLL at: $targetPath');
|
|
||||||
}
|
|
||||||
|
|
||||||
debugPrint('Verifying DLL exists at: $targetPath');
|
|
||||||
if (await targetFile.exists()) {
|
|
||||||
final fileSize = await targetFile.length();
|
|
||||||
debugPrint('DLL exists, size: $fileSize bytes');
|
|
||||||
} else {
|
|
||||||
debugPrint('DLL does not exist at target path!');
|
|
||||||
}
|
|
||||||
|
|
||||||
debugPrint('=====================================================\n');
|
|
||||||
return targetPath;
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
debugPrint('Error setting up ICU extension:');
|
|
||||||
debugPrint('Error: $e');
|
|
||||||
debugPrint('Stack trace: $stackTrace');
|
|
||||||
debugPrint('=====================================================\n');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add this method to check ICU status with visible feedback
|
// Add this method to check ICU status with visible feedback
|
||||||
static Future<bool> checkAndShowIcuStatus() async {
|
static Future<bool> checkAndShowIcuStatus() async {
|
||||||
final status = _hasIcuSupport;
|
final status = _hasIcuSupport;
|
||||||
@@ -452,122 +371,47 @@ END;
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the FTS match expression for each term
|
// Process terms for FTS5 query using proper tokenization
|
||||||
List<String> matchExpressions = terms.map((term) {
|
String ftsQuery = terms
|
||||||
// Remove dangerous characters but preserve Unicode
|
.map((term) {
|
||||||
String sanitizedTerm = term.replaceAll(RegExp(r'''['\\]'''), '');
|
// Remove dangerous characters but preserve Unicode
|
||||||
if (sanitizedTerm.isEmpty) return '';
|
String sanitizedTerm = term.replaceAll(RegExp(r'''['"]'''), '');
|
||||||
|
if (sanitizedTerm.isEmpty) return '';
|
||||||
// Escape special characters in the term for the LIKE pattern
|
|
||||||
String escapedTerm = sanitizedTerm
|
|
||||||
.replaceAll('%', '\\%')
|
|
||||||
.replaceAll('_', '\\_');
|
|
||||||
|
|
||||||
// Use LIKE for prefix/suffix matching
|
// Use proper FTS5 syntax: each word becomes a separate token with prefix matching
|
||||||
return '''(
|
List<String> words = sanitizedTerm.split(RegExp(r'\s+'));
|
||||||
content LIKE '%' || ? || '%' COLLATE NOCASE OR
|
return words.map((word) => '$word*').join(' OR ');
|
||||||
content LIKE ? || '%' COLLATE NOCASE OR
|
})
|
||||||
content LIKE '%' || ? COLLATE NOCASE
|
.where((term) => term.isNotEmpty)
|
||||||
)''';
|
.join(' AND ');
|
||||||
}).where((expr) => expr.isNotEmpty).toList();
|
|
||||||
|
|
||||||
if (matchExpressions.isEmpty) {
|
if (ftsQuery.isEmpty) {
|
||||||
debugPrint('Query was sanitized to empty string');
|
debugPrint('Query was sanitized to empty string');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combine all terms with AND logic
|
debugPrint('FTS query: "$ftsQuery"');
|
||||||
String whereClause = matchExpressions.join(' AND ');
|
|
||||||
|
|
||||||
// Create the parameter list (each term needs to be repeated 3 times for the different LIKE patterns)
|
|
||||||
List<String> parameters = [];
|
|
||||||
for (String term in terms) {
|
|
||||||
String sanitizedTerm = term.replaceAll(RegExp(r'''['\\]'''), '');
|
|
||||||
if (sanitizedTerm.isNotEmpty) {
|
|
||||||
parameters.addAll([sanitizedTerm, sanitizedTerm, sanitizedTerm]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debugPrint('Search query: "$query" with ${terms.length} terms');
|
// Execute the FTS query
|
||||||
debugPrint('Where clause: $whereClause');
|
|
||||||
debugPrint('Parameters: $parameters');
|
|
||||||
|
|
||||||
// Execute the search query
|
|
||||||
final List<Map<String, dynamic>> results = await db.rawQuery(
|
final List<Map<String, dynamic>> results = await db.rawQuery(
|
||||||
'''
|
'''
|
||||||
SELECT n.id, n.date, n.content
|
SELECT n.id, n.date, n.content,
|
||||||
FROM notes n
|
snippet(notes_fts, -1, '<b>', '</b>', '...', 64) as snippet
|
||||||
WHERE $whereClause
|
FROM notes_fts
|
||||||
ORDER BY n.date DESC
|
JOIN notes n ON notes_fts.rowid = n.id
|
||||||
|
WHERE notes_fts MATCH ?
|
||||||
|
ORDER BY rank
|
||||||
LIMIT 100
|
LIMIT 100
|
||||||
''',
|
''',
|
||||||
parameters,
|
[ftsQuery],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add snippets with highlighting in Dart code
|
|
||||||
final processedResults = results.map((row) {
|
|
||||||
final content = row['content'] as String;
|
|
||||||
final snippet = _createSnippet(content, terms);
|
|
||||||
return {
|
|
||||||
...row,
|
|
||||||
'snippet': snippet,
|
|
||||||
};
|
|
||||||
}).toList();
|
|
||||||
|
|
||||||
debugPrint('Search returned ${results.length} results');
|
debugPrint('Search returned ${results.length} results');
|
||||||
return processedResults;
|
return results;
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
debugPrint('Search failed: $e');
|
debugPrint('Search failed: $e');
|
||||||
debugPrint('Stack trace: $stackTrace');
|
debugPrint('Stack trace: $stackTrace');
|
||||||
rethrow;
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to create a snippet with highlighted terms
|
|
||||||
static String _createSnippet(String content, List<String> terms) {
|
|
||||||
const int snippetLength = 150; // Maximum length of the snippet
|
|
||||||
const String ellipsis = '...';
|
|
||||||
|
|
||||||
// Find the first match position
|
|
||||||
int firstMatchPos = content.length;
|
|
||||||
String? firstMatchTerm;
|
|
||||||
|
|
||||||
for (final term in terms) {
|
|
||||||
final pos = content.toLowerCase().indexOf(term.toLowerCase());
|
|
||||||
if (pos != -1 && pos < firstMatchPos) {
|
|
||||||
firstMatchPos = pos;
|
|
||||||
firstMatchTerm = term;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstMatchTerm == null) {
|
|
||||||
// No matches found, return start of content
|
|
||||||
return content.length <= snippetLength
|
|
||||||
? content
|
|
||||||
: content.substring(0, snippetLength) + ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate snippet range around the first match
|
|
||||||
int start = (firstMatchPos - snippetLength ~/ 3).clamp(0, content.length);
|
|
||||||
int end = (start + snippetLength).clamp(0, content.length);
|
|
||||||
|
|
||||||
// Adjust start to not cut words
|
|
||||||
if (start > 0) {
|
|
||||||
start = content.lastIndexOf(RegExp(r'\s'), start) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the snippet
|
|
||||||
String snippet = content.substring(start, end);
|
|
||||||
if (start > 0) snippet = ellipsis + snippet;
|
|
||||||
if (end < content.length) snippet = snippet + ellipsis;
|
|
||||||
|
|
||||||
// Highlight all term matches in the snippet
|
|
||||||
for (final term in terms) {
|
|
||||||
final pattern = RegExp(RegExp.escape(term), caseSensitive: false);
|
|
||||||
snippet = snippet.replaceAllMapped(pattern,
|
|
||||||
(match) => '<b>${match.group(0)}</b>');
|
|
||||||
}
|
|
||||||
|
|
||||||
return snippet;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1090,7 +1090,7 @@ class MainPageState extends State<MainPage> with WindowListener {
|
|||||||
border: const OutlineInputBorder(),
|
border: const OutlineInputBorder(),
|
||||||
filled:
|
filled:
|
||||||
_currentlyDisplayedNote?.displayDate !=
|
_currentlyDisplayedNote?.displayDate !=
|
||||||
previousNote?.displayDate,
|
previousNote?.displayDate,
|
||||||
fillColor:
|
fillColor:
|
||||||
_currentlyDisplayedNote?.displayDate !=
|
_currentlyDisplayedNote?.displayDate !=
|
||||||
previousNote?.displayDate
|
previousNote?.displayDate
|
||||||
|
Reference in New Issue
Block a user