[geary/mjog/334-libstemmer: 2/2] engine: Remove in-tree unicodesn stemmer
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/mjog/334-libstemmer: 2/2] engine: Remove in-tree unicodesn stemmer
- Date: Sun, 13 Sep 2020 14:55:18 +0000 (UTC)
commit 6e6a9478302506338b83bf4960150dc6730e5845
Author: Michael Gratton <mike vee net>
Date: Sun Sep 13 23:46:53 2020 +1000
engine: Remove in-tree unicodesn stemmer
Now that search is using libstemmer, the in-tree stemmer is no longer
needed and can be removed.
COPYING.snowball | 23 -
sql/meson.build | 1 +
sql/version-011.sql | 17 +-
sql/version-029.sql | 5 +
src/client/meson.build | 4 -
src/engine/imap-db/imap-db-database.vala | 101 +-
src/engine/imap-db/imap-db-sqlite.c | 143 ++
src/engine/meson.build | 5 +-
src/meson.build | 3 +-
src/sqlite3-unicodesn/Makefile | 57 -
src/sqlite3-unicodesn/README | 33 -
src/sqlite3-unicodesn/extension.c | 68 -
src/sqlite3-unicodesn/fts3Int.h | 35 -
src/sqlite3-unicodesn/fts3_tokenizer.h | 161 --
src/sqlite3-unicodesn/fts3_unicode2.c | 366 ----
src/sqlite3-unicodesn/fts3_unicodesn.c | 583 ------
src/sqlite3-unicodesn/fts3_unicodesn.h | 21 -
src/sqlite3-unicodesn/libstemmer_c/MANIFEST | 72 -
src/sqlite3-unicodesn/libstemmer_c/Makefile | 9 -
src/sqlite3-unicodesn/libstemmer_c/README | 125 --
.../libstemmer_c/examples/stemwords.c | 209 --
.../libstemmer_c/include/libstemmer.h | 79 -
.../libstemmer_c/libstemmer/libstemmer.c | 95 -
.../libstemmer_c/libstemmer/libstemmer_c.in | 95 -
.../libstemmer_c/libstemmer/libstemmer_utf8.c | 95 -
.../libstemmer_c/libstemmer/modules.h | 190 --
.../libstemmer_c/libstemmer/modules.txt | 50 -
.../libstemmer_c/libstemmer/modules_utf8.h | 121 --
.../libstemmer_c/libstemmer/modules_utf8.txt | 49 -
src/sqlite3-unicodesn/libstemmer_c/mkinc.mak | 82 -
src/sqlite3-unicodesn/libstemmer_c/mkinc_utf8.mak | 52 -
src/sqlite3-unicodesn/libstemmer_c/runtime/api.h | 26 -
.../libstemmer_c/runtime/api_sq3.c | 75 -
.../libstemmer_c/runtime/header.h | 58 -
.../libstemmer_c/runtime/utilities_sq3.c | 480 -----
.../libstemmer_c/src_c/stem_ISO_8859_1_danish.c | 337 ---
.../libstemmer_c/src_c/stem_ISO_8859_1_danish.h | 16 -
.../libstemmer_c/src_c/stem_ISO_8859_1_dutch.c | 624 ------
.../libstemmer_c/src_c/stem_ISO_8859_1_dutch.h | 16 -
.../libstemmer_c/src_c/stem_ISO_8859_1_english.c | 1117 ----------
.../libstemmer_c/src_c/stem_ISO_8859_1_english.h | 16 -
.../libstemmer_c/src_c/stem_ISO_8859_1_finnish.c | 762 -------
.../libstemmer_c/src_c/stem_ISO_8859_1_finnish.h | 16 -
.../libstemmer_c/src_c/stem_ISO_8859_1_french.c | 1246 -----------
.../libstemmer_c/src_c/stem_ISO_8859_1_french.h | 16 -
.../libstemmer_c/src_c/stem_ISO_8859_1_german.c | 521 -----
.../libstemmer_c/src_c/stem_ISO_8859_1_german.h | 16 -
.../libstemmer_c/src_c/stem_ISO_8859_1_hungarian.c | 1230 -----------
.../libstemmer_c/src_c/stem_ISO_8859_1_hungarian.h | 16 -
.../libstemmer_c/src_c/stem_ISO_8859_1_italian.c | 1065 ----------
.../libstemmer_c/src_c/stem_ISO_8859_1_italian.h | 16 -
.../libstemmer_c/src_c/stem_ISO_8859_1_norwegian.c | 297 ---
.../libstemmer_c/src_c/stem_ISO_8859_1_norwegian.h | 16 -
.../libstemmer_c/src_c/stem_ISO_8859_1_porter.c | 749 -------
.../libstemmer_c/src_c/stem_ISO_8859_1_porter.h | 16 -
.../src_c/stem_ISO_8859_1_portuguese.c | 1017 ---------
.../src_c/stem_ISO_8859_1_portuguese.h | 16 -
.../libstemmer_c/src_c/stem_ISO_8859_1_spanish.c | 1093 ----------
.../libstemmer_c/src_c/stem_ISO_8859_1_spanish.h | 16 -
.../libstemmer_c/src_c/stem_ISO_8859_1_swedish.c | 307 ---
.../libstemmer_c/src_c/stem_ISO_8859_1_swedish.h | 16 -
.../libstemmer_c/src_c/stem_ISO_8859_2_romanian.c | 998 ---------
.../libstemmer_c/src_c/stem_ISO_8859_2_romanian.h | 16 -
.../libstemmer_c/src_c/stem_KOI8_R_russian.c | 700 -------
.../libstemmer_c/src_c/stem_KOI8_R_russian.h | 16 -
.../libstemmer_c/src_c/stem_UTF_8_danish.c | 339 ---
.../libstemmer_c/src_c/stem_UTF_8_danish.h | 16 -
.../libstemmer_c/src_c/stem_UTF_8_dutch.c | 634 ------
.../libstemmer_c/src_c/stem_UTF_8_dutch.h | 16 -
.../libstemmer_c/src_c/stem_UTF_8_english.c | 1125 ----------
.../libstemmer_c/src_c/stem_UTF_8_english.h | 16 -
.../libstemmer_c/src_c/stem_UTF_8_finnish.c | 768 -------
.../libstemmer_c/src_c/stem_UTF_8_finnish.h | 16 -
.../libstemmer_c/src_c/stem_UTF_8_french.c | 1256 -----------
.../libstemmer_c/src_c/stem_UTF_8_french.h | 16 -
.../libstemmer_c/src_c/stem_UTF_8_german.c | 527 -----
.../libstemmer_c/src_c/stem_UTF_8_german.h | 16 -
.../libstemmer_c/src_c/stem_UTF_8_hungarian.c | 1234 -----------
.../libstemmer_c/src_c/stem_UTF_8_hungarian.h | 16 -
.../libstemmer_c/src_c/stem_UTF_8_italian.c | 1073 ----------
.../libstemmer_c/src_c/stem_UTF_8_italian.h | 16 -
.../libstemmer_c/src_c/stem_UTF_8_norwegian.c | 299 ---
.../libstemmer_c/src_c/stem_UTF_8_norwegian.h | 16 -
.../libstemmer_c/src_c/stem_UTF_8_porter.c | 755 -------
.../libstemmer_c/src_c/stem_UTF_8_porter.h | 16 -
.../libstemmer_c/src_c/stem_UTF_8_portuguese.c | 1023 ---------
.../libstemmer_c/src_c/stem_UTF_8_portuguese.h | 16 -
.../libstemmer_c/src_c/stem_UTF_8_romanian.c | 1004 ---------
.../libstemmer_c/src_c/stem_UTF_8_romanian.h | 16 -
.../libstemmer_c/src_c/stem_UTF_8_russian.c | 694 ------
.../libstemmer_c/src_c/stem_UTF_8_russian.h | 16 -
.../libstemmer_c/src_c/stem_UTF_8_spanish.c | 1097 ----------
.../libstemmer_c/src_c/stem_UTF_8_spanish.h | 16 -
.../libstemmer_c/src_c/stem_UTF_8_swedish.c | 309 ---
.../libstemmer_c/src_c/stem_UTF_8_swedish.h | 16 -
.../libstemmer_c/src_c/stem_UTF_8_turkish.c | 2205 --------------------
.../libstemmer_c/src_c/stem_UTF_8_turkish.h | 16 -
src/sqlite3-unicodesn/meson.build | 48 -
src/sqlite3-unicodesn/static.c | 55 -
test/engine/imap-db/imap-db-database-test.vala | 3 +-
100 files changed, 178 insertions(+), 30417 deletions(-)
---
diff --git a/sql/meson.build b/sql/meson.build
index 5b3813903..6bf30dcaf 100644
--- a/sql/meson.build
+++ b/sql/meson.build
@@ -27,6 +27,7 @@ sql_files = [
'version-026.sql',
'version-027.sql',
'version-028.sql',
+ 'version-029.sql',
]
install_data(sql_files,
diff --git a/sql/version-011.sql b/sql/version-011.sql
index 0bfc39a10..82b96518b 100644
--- a/sql/version-011.sql
+++ b/sql/version-011.sql
@@ -1,5 +1,16 @@
--
--- Dummy database upgrade to add MessageSearchTable, whose parameters depend on
--- things we need at run-time. See src/engine/imap-db/imap-db-database.vala in
--- post_upgrade() for the code that runs the upgrade.
+-- Create MessageSearchTable
--
+
+CREATE VIRTUAL TABLE MessageSearchTable USING fts4(
+ body,
+ attachment,
+ subject,
+ from_field,
+ receivers,
+ cc,
+ bcc,
+
+ tokenize=simple,
+ prefix="2,4,6,8,10",
+);
diff --git a/sql/version-029.sql b/sql/version-029.sql
new file mode 100644
index 000000000..3f58ad9d8
--- /dev/null
+++ b/sql/version-029.sql
@@ -0,0 +1,5 @@
+--
+-- Use libstemmer for stemming rather than SQLite.
+--
+
+DROP TABLE IF EXISTS TokenizerTable;
diff --git a/src/client/meson.build b/src/client/meson.build
index 088f4e47d..b9eadad32 100644
--- a/src/client/meson.build
+++ b/src/client/meson.build
@@ -232,7 +232,6 @@ client_internal_header_fixup = custom_target(
client_dep = declare_dependency(
link_with: [
client_lib,
- sqlite3_unicodesn_lib
],
include_directories: include_directories('.')
)
@@ -244,9 +243,6 @@ client_internal_dep = declare_dependency(
'-L' + client_build_dir,
'-l' + client_package
],
- link_with: [
- sqlite3_unicodesn_lib,
- ],
include_directories: include_directories('.'),
sources: client_internal_header_fixup
)
diff --git a/src/engine/imap-db/imap-db-database.vala b/src/engine/imap-db/imap-db-database.vala
index c7df463b2..cd428c89e 100644
--- a/src/engine/imap-db/imap-db-database.vala
+++ b/src/engine/imap-db/imap-db-database.vala
@@ -7,7 +7,7 @@
[CCode (cname = "g_utf8_collate_key")]
extern string utf8_collate_key(string data, ssize_t len);
-extern int sqlite3_unicodesn_register_tokenizer(Sqlite.Database db);
+extern int sqlite3_register_legacy_tokenizer(Sqlite.Database db);
private class Geary.ImapDB.Database : Geary.Db.VersionedDatabase {
@@ -73,6 +73,7 @@ private class Geary.ImapDB.Database : Geary.Db.VersionedDatabase {
private ProgressMonitor upgrade_monitor;
private ProgressMonitor vacuum_monitor;
private bool new_db = false;
+ private bool is_open_in_progress = false;
private GC? gc = null;
private Cancellable gc_cancellable = new Cancellable();
@@ -93,7 +94,9 @@ private class Geary.ImapDB.Database : Geary.Db.VersionedDatabase {
*/
public new async void open(Db.DatabaseFlags flags, Cancellable? cancellable)
throws Error {
+ this.is_open_in_progress = true;
yield base.open(flags, cancellable);
+ this.is_open_in_progress = false;
yield run_gc(NONE, null, cancellable);
}
@@ -251,10 +254,6 @@ private class Geary.ImapDB.Database : Geary.Db.VersionedDatabase {
yield post_upgrade_encode_folder_names(cancellable);
break;
- case 11:
- yield post_upgrade_add_search_table(cancellable);
- break;
-
case 12:
yield post_upgrade_populate_internal_date_time_t(cancellable);
break;
@@ -282,10 +281,6 @@ private class Geary.ImapDB.Database : Geary.Db.VersionedDatabase {
case 22:
yield post_upgrade_rebuild_attachments(cancellable);
break;
-
- case 23:
- yield post_upgrade_add_tokenizer_table(cancellable);
- break;
}
}
@@ -317,67 +312,6 @@ private class Geary.ImapDB.Database : Geary.Db.VersionedDatabase {
}, cancellable);
}
- // Version 11.
- private async void post_upgrade_add_search_table(Cancellable? cancellable)
- throws Error {
- yield exec_transaction_async(Db.TransactionType.RW, (cx) => {
- string stemmer = find_appropriate_search_stemmer();
- debug("Creating search table using %s stemmer", stemmer);
-
- // This can't go in the .sql file because its schema (the stemmer
- // algorithm) is determined at runtime.
- cx.exec("""
- CREATE VIRTUAL TABLE MessageSearchTable USING fts4(
- body,
- attachment,
- subject,
- from_field,
- receivers,
- cc,
- bcc,
-
- tokenize=unicodesn "stemmer=%s",
- prefix="2,4,6,8,10",
- );
- """.printf(stemmer));
- return Geary.Db.TransactionOutcome.COMMIT;
- }, cancellable);
- }
-
- private string find_appropriate_search_stemmer() {
- // Unfortunately, the stemmer library only accepts the full language
- // name for the stemming algorithm. This translates between the user's
- // preferred language ISO 639-1 code and our available stemmers.
- // FIXME: the available list here is determined by what's included in
- // src/sqlite3-unicodesn/CMakeLists.txt. We should pass that list in
- // instead of hardcoding it here.
- foreach (string l in Intl.get_language_names()) {
- switch (l) {
- case "da": return "danish";
- case "nl": return "dutch";
- case "en": return "english";
- case "fi": return "finnish";
- case "fr": return "french";
- case "de": return "german";
- case "hu": return "hungarian";
- case "it": return "italian";
- case "no": return "norwegian";
- case "pt": return "portuguese";
- case "ro": return "romanian";
- case "ru": return "russian";
- case "es": return "spanish";
- case "sv": return "swedish";
- case "tr": return "turkish";
- }
- }
-
- // Default to English because it seems to be on average the language
- // most likely to be present in emails, regardless of the user's
- // language setting. This is not an exact science, and search results
- // should be ok either way in most cases.
- return "english";
- }
-
// Versions 12 and 18.
private async void
post_upgrade_populate_internal_date_time_t(Cancellable? cancellable)
@@ -635,25 +569,6 @@ private class Geary.ImapDB.Database : Geary.Db.VersionedDatabase {
}, cancellable);
}
- // Version 23
- private async void post_upgrade_add_tokenizer_table(Cancellable? cancellable)
- throws Error {
- yield exec_transaction_async(Db.TransactionType.RW, (cx) => {
- string stemmer = find_appropriate_search_stemmer();
- debug("Creating tokenizer table using %s stemmer", stemmer);
-
- // These can't go in the .sql file because its schema (the stemmer
- // algorithm) is determined at runtime.
- cx.exec("""
- CREATE VIRTUAL TABLE TokenizerTable USING fts3tokenize(
- unicodesn,
- "stemmer=%s"
- );
- """.printf(stemmer));
- return Db.TransactionOutcome.COMMIT;
- }, cancellable);
- }
-
/**
* Determines if the database's FTS table indexes are valid.
*/
@@ -706,7 +621,13 @@ private class Geary.ImapDB.Database : Geary.Db.VersionedDatabase {
cx.set_foreign_keys(true);
cx.set_recursive_triggers(true);
cx.set_synchronous(Db.SynchronousMode.NORMAL);
- sqlite3_unicodesn_register_tokenizer(cx.db);
+
+ if (this.is_open_in_progress) {
+ // Register a tokenizer with old "unicodesn" name so that
+ // upgrades for existing databases that still reference it
+ // don't fail.
+ sqlite3_register_legacy_tokenizer(cx.db);
+ }
if (cx.db.create_function(
UTF8_CASE_INSENSITIVE_FN,
diff --git a/src/engine/imap-db/imap-db-sqlite.c b/src/engine/imap-db/imap-db-sqlite.c
new file mode 100644
index 000000000..5f9f06259
--- /dev/null
+++ b/src/engine/imap-db/imap-db-sqlite.c
@@ -0,0 +1,143 @@
+/*
+ * Parts of the code below have been taken from
+ * `ext/fts3/fts3_tokenizer.h` SQLite source code repository with the
+ * following copyright notice:
+ *
+ * "The author disclaims copyright to this source code."
+ *
+ * Parts of this code have been taken from
+ * <https://www.sqlite.org/fts3.html>, which carries the following
+ * copyright notice:
+ *
+ * All of the code and documentation in SQLite has been dedicated to
+ * the public domain by the authors. All code authors, and
+ * representatives of the companies they work for, have signed
+ * affidavits dedicating their contributions to the public domain and
+ * originals of those signed affidavits are stored in a firesafe at
+ * the main offices of Hwaci. Anyone is free to copy, modify, publish,
+ * use, compile, sell, or distribute the original SQLite code, either
+ * in source code form or as a compiled binary, for any purpose,
+ * commercial or non-commercial, and by any means.
+ *
+ * --- <https://www.sqlite.org/copyright.html>
+ */
+
+/*
+ * Defines SQLite FTS3/4 tokeniser with the same name as the one used
+ * in Geary prior to version 3.40, so that database upgrades that
+ * still reference this tokeniser can complete successfully.
+ */
+
+#define TOKENIZER_NAME "unicodesn"
+
+#include <sqlite3.h>
+#include <string.h>
+
+#ifndef _FTS3_TOKENIZER_H_
+#define _FTS3_TOKENIZER_H_
+
+typedef struct sqlite3_tokenizer_module sqlite3_tokenizer_module;
+typedef struct sqlite3_tokenizer sqlite3_tokenizer;
+typedef struct sqlite3_tokenizer_cursor sqlite3_tokenizer_cursor;
+
+struct sqlite3_tokenizer_module {
+ int iVersion;
+ int (*xCreate)(
+ int argc, /* Size of argv array */
+ const char *const*argv, /* Tokenizer argument strings */
+ sqlite3_tokenizer **ppTokenizer /* OUT: Created tokenizer */
+ );
+ int (*xDestroy)(sqlite3_tokenizer *pTokenizer);
+ int (*xOpen)(
+ sqlite3_tokenizer *pTokenizer, /* Tokenizer object */
+ const char *pInput, int nBytes, /* Input buffer */
+ sqlite3_tokenizer_cursor **ppCursor /* OUT: Created tokenizer cursor */
+ );
+ int (*xClose)(sqlite3_tokenizer_cursor *pCursor);
+ int (*xNext)(
+ sqlite3_tokenizer_cursor *pCursor, /* Tokenizer cursor */
+ const char **ppToken, int *pnBytes, /* OUT: Normalized text for token */
+ int *piStartOffset, /* OUT: Byte offset of token in input buffer */
+ int *piEndOffset, /* OUT: Byte offset of end of token in input buffer */
+ int *piPosition /* OUT: Number of tokens returned before this one */
+ );
+ int (*xLanguageid)(sqlite3_tokenizer_cursor *pCsr, int iLangid);
+};
+
+struct sqlite3_tokenizer {
+ const sqlite3_tokenizer_module *pModule; /* The module for this tokenizer */
+ /* Tokenizer implementations will typically add additional fields */
+};
+
+struct sqlite3_tokenizer_cursor {
+ sqlite3_tokenizer *pTokenizer; /* Tokenizer for this cursor. */
+ /* Tokenizer implementations will typically add additional fields */
+};
+
+int fts3_global_term_cnt(int iTerm, int iCol);
+int fts3_term_cnt(int iTerm, int iCol);
+
+
+#endif /* _FTS3_TOKENIZER_H_ */
+
+static int registerTokenizer(
+ sqlite3 *db,
+ char *zName,
+ const sqlite3_tokenizer_module *p
+){
+ int rc;
+ sqlite3_stmt *pStmt;
+ const char *zSql = "SELECT fts3_tokenizer(?, ?)";
+
+ /* Enable the 2-argument form of fts3_tokenizer in SQLite >= 3.12 */
+ rc = sqlite3_db_config(db,SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER,1,0);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC);
+ sqlite3_bind_blob(pStmt, 2, &p, sizeof(p), SQLITE_STATIC);
+ sqlite3_step(pStmt);
+
+ return sqlite3_finalize(pStmt);
+}
+
+int queryTokenizer(
+ sqlite3 *db,
+ char *zName,
+ const sqlite3_tokenizer_module **pp
+){
+ int rc;
+ sqlite3_stmt *pStmt;
+ const char *zSql = "SELECT fts3_tokenizer(?)";
+
+ *pp = 0;
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ if( rc!=SQLITE_OK ){
+ return rc;
+ }
+
+ sqlite3_bind_text(pStmt, 1, zName, -1, SQLITE_STATIC);
+ if( SQLITE_ROW==sqlite3_step(pStmt) ){
+ if( sqlite3_column_type(pStmt, 0)==SQLITE_BLOB ){
+ memcpy(pp, sqlite3_column_blob(pStmt, 0), sizeof(*pp));
+ }
+ }
+
+ return sqlite3_finalize(pStmt);
+}
+
+#include <stdio.h>
+
+int sqlite3_register_legacy_tokenizer(sqlite3 *db) {
+ static const sqlite3_tokenizer_module *tokenizer = 0;
+ if (!tokenizer) {
+ queryTokenizer(db, "simple", &tokenizer);
+ }
+ return registerTokenizer(db, TOKENIZER_NAME, tokenizer);
+}
diff --git a/src/engine/meson.build b/src/engine/meson.build
index 226007d52..69fadfce2 100644
--- a/src/engine/meson.build
+++ b/src/engine/meson.build
@@ -179,6 +179,7 @@ engine_vala_sources = files(
'imap-db/imap-db-gc.vala',
'imap-db/imap-db-message-row.vala',
'imap-db/imap-db-search-query.vala',
+ 'imap-db/imap-db-sqlite.c',
'imap-engine/imap-engine.vala',
'imap-engine/imap-engine-account-operation.vala',
@@ -386,7 +387,6 @@ engine_internal_header_fixup = custom_target(
engine_dep = declare_dependency(
link_with: [
engine_lib,
- sqlite3_unicodesn_lib
],
include_directories: include_directories('.')
)
@@ -398,9 +398,6 @@ engine_internal_dep = declare_dependency(
'-L' + engine_build_dir,
'-lgeary-engine'
],
- link_with: [
- sqlite3_unicodesn_lib,
- ],
include_directories: include_directories('.'),
sources: engine_internal_header_fixup
)
diff --git a/src/meson.build b/src/meson.build
index f8e2fbeaf..07dd2d8be 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -49,9 +49,10 @@ geary_c_args = [
'-DGCK_API_SUBJECT_TO_CHANGE',
'-DGCR_API_SUBJECT_TO_CHANGE',
'-DGOA_API_IS_SUBJECT_TO_CHANGE',
+ '-DSQLITE_ENABLE_FTS4',
+ '-DSQLITE_ENABLE_FTS4_UNICODE61'
]
-subdir('sqlite3-unicodesn')
subdir('engine')
subdir('client')
subdir('console')
diff --git a/test/engine/imap-db/imap-db-database-test.vala b/test/engine/imap-db/imap-db-database-test.vala
index f5f3702c4..9d69ac19b 100644
--- a/test/engine/imap-db/imap-db-database-test.vala
+++ b/test/engine/imap-db/imap-db-database-test.vala
@@ -1,4 +1,3 @@
-
/*
* Copyright 2018 Michael Gratton <mike vee net>
*
@@ -107,7 +106,7 @@ class Geary.ImapDB.DatabaseTest : TestCase {
);
db.open.end(async_result());
- assert_equal<int?>(db.get_schema_version(), 28, "Post-upgrade version");
+ assert_equal<int?>(db.get_schema_version(), 29, "Post-upgrade version");
// Since schema v22 deletes the re-creates all attachments,
// attachment 12 should no longer exist on the file system and
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]