about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.guix/guile-xapian-package.scm7
-rw-r--r--configure.ac3
-rw-r--r--tests/smoketest.scm28
-rw-r--r--xapian-head.i80
-rw-r--r--xapian-headers.i163
5 files changed, 148 insertions, 133 deletions
diff --git a/.guix/guile-xapian-package.scm b/.guix/guile-xapian-package.scm
index e8394b5..ecf8ca5 100644
--- a/.guix/guile-xapian-package.scm
+++ b/.guix/guile-xapian-package.scm
@@ -21,6 +21,7 @@
   #:use-module ((gnu packages autotools) #:select (autoconf autoconf-archive automake libtool))
   #:use-module ((gnu packages guile) #:select (guile-2.2))
   #:use-module ((gnu packages guile-xyz) #:prefix guix:)
+  #:use-module ((gnu packages search) #:select (xapian))
   #:use-module ((gnu packages swig) #:select (swig))
   #:use-module (guix gexp)
   #:use-module (guix git-download)
@@ -35,6 +36,9 @@
                         #:recursive? #t
                         #:select? (or (git-predicate (dirname (current-source-directory)))
                                       (const #t))))
+    (inputs
+     (modify-inputs (package-inputs guix:guile-xapian)
+       (replace "xapian" xapian)))
     (native-inputs
      (modify-inputs (package-native-inputs guix:guile-xapian)
        (prepend autoconf autoconf-archive automake libtool)
@@ -48,6 +52,9 @@
                         #:recursive? #t
                         #:select? (or (git-predicate (dirname (current-source-directory)))
                                       (const #t))))
+    (inputs
+     (modify-inputs (package-inputs guix:guile2.2-xapian)
+       (replace "xapian" xapian)))
     (native-inputs
      (modify-inputs (package-native-inputs guix:guile2.2-xapian)
        (prepend autoconf autoconf-archive automake libtool)
diff --git a/configure.ac b/configure.ac
index 7573248..82889a2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -23,6 +23,7 @@ AM_SILENT_RULES([yes])
 LT_INIT
 
 AC_PROG_CXX
+AX_CXX_COMPILE_STDCXX(17)
 AX_PKG_SWIG([4.2], [], [ AC_MSG_ERROR([swig >= 4.2 is required]) ])
 GUILE_PKG([3.0 2.2])
 PKG_CHECK_MODULES([GUILE], [guile-3.0],,[
@@ -30,7 +31,7 @@ PKG_CHECK_MODULES([GUILE], [guile-3.0],,[
 ])
 GUILE_PROGS
 GUILE_MODULE_REQUIRED([htmlprag])
-PKG_CHECK_MODULES([XAPIAN], [xapian-core < 2])
+PKG_CHECK_MODULES([XAPIAN], [xapian-core >= 2])
 PKG_CHECK_MODULES([ZLIB], [zlib])
 
 # For tests
diff --git a/tests/smoketest.scm b/tests/smoketest.scm
index 1e4f37e..b7a84b8 100644
--- a/tests/smoketest.scm
+++ b/tests/smoketest.scm
@@ -18,7 +18,7 @@
 ;;; <https://www.gnu.org/licenses/>.
 
 ;;; This test suite is a more or less faithful port of
-;;; xapian-bindings/python3/smoketest.py from v1.4.32 of the xapian
+;;; xapian-bindings/python3/smoketest.py from v2.1.0 of the xapian
 ;;; repo. Keep this updated with new releases of xapian.
 
 (use-modules ((rnrs conditions) #:select (&error) #:prefix rnrs:)
@@ -79,12 +79,16 @@ after."
   "Xapian::Stem(english)"
   (Stem-get-description (make-stem "english")))
 
-(test-expect-fail "document does not transparently handle a zero byte")
-(let* ((data "a\0b")
-       (doc (make-document #:data data)))
-  (test-equal "document does not transparently handle a zero byte"
-    data
-    (document-data (make-document #:data data))))
+;; scm_to_stringn does not allow strings containing null characters to
+;; be converted to C strings. So, this test can never actually pass.
+;; And, we keep it commented out because running it raises an
+;; exception and crashes the test runner process.
+;; (test-expect-fail "document does not transparently handle a zero byte")
+;; (let* ((data "a\0b")
+;;        (doc (make-document #:data data)))
+;;   (test-equal "document does not transparently handle a zero byte"
+;;     data
+;;     (document-data (make-document #:data data))))
 
 (call-with-temporary-writable-database "/tmp/xapian-db"
   (lambda (db)
@@ -185,15 +189,15 @@ after."
 ;; TODO: Check QueryParser parsing error.
 
 (test-equal "parse pure NOT query"
-  "Query((<alldocuments> AND_NOT test@1))"
+  "Query((0 * <alldocuments> AND_NOT test@1))"
   (Query-get-description (parse-query "NOT test"
                                       #:stemmer (make-stem "none")
                                       #:boolean? #t
                                       #:pure-not? #t)))
 
 (test-equal "parse query with partial matching 1"
-  "Query((Zfoo@1 AND (WILDCARD SYNONYM o OR Zo@2)))"
-  (Query-get-description (parse-query "foo o"
+  "Query((Zfoo@1 AND (WILDCARD SYNONYM ox OR Zox@2)))"
+  (Query-get-description (parse-query "foo ox"
                                       #:stemmer (make-stem "en")
                                       #:default-operator (Query-OP-AND)
                                       #:partial? #t)))
@@ -210,7 +214,7 @@ after."
   (Query-get-description (query-or (query "foo") (query "bar£"))))
 
 (test-equal "parse query with unicode characters"
-  "Query((<alldocuments> AND_NOT Zt\u00e9st@1))"
+  "Query((0 * <alldocuments> AND_NOT Zt\u00e9st@1))"
   (Query-get-description (parse-query "NOT tést"
                                       #:stemmer (make-stem "en")
                                       #:boolean? #t
@@ -264,8 +268,6 @@ after."
                 #:stemmer (make-stem "none")
                 #:range-processors (list (prefixed-date-range-processor 1 #:prefer-mdy? #t)))))
 
-;; TODO: Test NumberValueRangeProcessor.
-
 (let* ((test-field-processor
         (field-processor (lambda (str)
                            (if (string= str "spam")
diff --git a/xapian-head.i b/xapian-head.i
index c079f93..65b99df 100644
--- a/xapian-head.i
+++ b/xapian-head.i
@@ -1,8 +1,8 @@
 %{
-/** @file xapian-head.i
+/** @file
  * @brief Header for SWIG interface file for Xapian.
  */
-/* Copyright (C) 2005,2006,2007,2008,2009,2011,2012,2013,2014,2015,2016 Olly Betts
+/* Copyright (C) 2005,2006,2007,2008,2009,2011,2012,2013,2014,2015,2016,2017 Olly Betts
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -15,9 +15,8 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
- * USA
+ * along with this program; if not, see
+ * <https://www.gnu.org/licenses/>.
  */
 
 // Disable any deprecation warnings for Xapian methods/functions/classes.
@@ -32,53 +31,28 @@
 
 using namespace std;
 
-// If a backend has been disabled in xapian-core (manually or automatically) we
-// include a stub definition here so the bindings can still be built.
+// If the remote backend has been disabled in xapian-core (manually or
+// automatically) we include a stub definition here so the bindings can still
+// be built.
 namespace Xapian {
-%}
-#ifndef XAPIAN_BINDINGS_SKIP_DEPRECATED_DB_FACTORIES
-%{
-
-#ifndef XAPIAN_HAS_CHERT_BACKEND
-    namespace Chert {
-	static Database open(const string &) {
-	    throw FeatureUnavailableError("Chert backend not supported");
-	}
-	static WritableDatabase open(const string &, int, int = 0) {
-	    throw FeatureUnavailableError("Chert backend not supported");
-	}
-    }
-#endif
-
-#ifndef XAPIAN_HAS_INMEMORY_BACKEND
-    namespace InMemory {
-	static WritableDatabase open() {
-	    throw FeatureUnavailableError("InMemory backend not supported");
-	}
-    }
-#endif
-
-%}
-#endif
-%{
 
 #ifndef XAPIAN_HAS_REMOTE_BACKEND
     namespace Remote {
-	static Database open(const string &, unsigned int, useconds_t = 0, useconds_t = 0) {
-	    throw FeatureUnavailableError("Remote backend not supported");
-	}
+        static Database open(const string &, unsigned int, unsigned = 0, unsigned = 0) {
+            throw FeatureUnavailableError("Remote backend not supported");
+        }
 
-	static WritableDatabase open_writable(const string &, unsigned int, useconds_t = 0, useconds_t = 0, int = 0) {
-	    throw FeatureUnavailableError("Remote backend not supported");
-	}
+        static WritableDatabase open_writable(const string &, unsigned int, unsigned = 0, unsigned = 0, int = 0) {
+            throw FeatureUnavailableError("Remote backend not supported");
+        }
 
-	static Database open(const string &, const string &, useconds_t = 0) {
-	    throw FeatureUnavailableError("Remote backend not supported");
-	}
+        static Database open(const string &, const string &, unsigned = 0) {
+            throw FeatureUnavailableError("Remote backend not supported");
+        }
 
-	static WritableDatabase open_writable(const string &, const string &, useconds_t = 0, int = 0) {
-	    throw FeatureUnavailableError("Remote backend not supported");
-	}
+        static WritableDatabase open_writable(const string &, const string &, unsigned = 0, int = 0) {
+            throw FeatureUnavailableError("Remote backend not supported");
+        }
     }
 #endif
 
@@ -89,6 +63,7 @@ using namespace std;
 
 %include exception.i
 %include stl.i
+%include std_string_view.i
 
 // Disable errors about not including headers individually.
 #define XAPIAN_IN_XAPIAN_H
@@ -99,15 +74,20 @@ using namespace std;
 #define XAPIAN_DEPRECATED_CLASS
 #define XAPIAN_DEPRECATED_CLASS_EX
 #define XAPIAN_VISIBILITY_DEFAULT
+#define XAPIAN_VISIBILITY_INTERNAL
 #define XAPIAN_CONST_FUNCTION
 #define XAPIAN_PURE_FUNCTION
-#define XAPIAN_NOEXCEPT
-#define XAPIAN_NOTHROW(D) D
+
+// This works around buggy behaviour in SWIG's preprocessor, and only works
+// because we currently only use XAPIAN_NONNULL() with an empty argument:
+//
+// https://github.com/swig/swig/pull/1111
+//
+// The correct version is:
+// #define XAPIAN_NONNULL(L)
+#define XAPIAN_NONNULL()
 
 // Ignore these which SWIG seems to add pointless type entries for due them
 // being used in the SWIG typemap for std::pair.
 %ignore first_type;
 %ignore second_type;
-
-// Treat POSIX useconds_t as unsigned.
-%apply unsigned { useconds_t };
diff --git a/xapian-headers.i b/xapian-headers.i
index 4b393a1..8ffb403 100644
--- a/xapian-headers.i
+++ b/xapian-headers.i
@@ -1,7 +1,7 @@
 %{
 /* xapian-headers.i: Getting SWIG to parse Xapian's C++ headers.
  *
- * Copyright 2004,2006,2011,2012,2013,2014,2015,2016,2019 Olly Betts
+ * Copyright 2004-2024 Olly Betts
  * Copyright 2014 Assem Chelli
  *
  * This program is free software; you can redistribute it and/or
@@ -15,9 +15,8 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
- * USA
+ * along with this program; if not, see
+ * <https://www.gnu.org/licenses/>.
  */
 %}
 
@@ -54,6 +53,7 @@
     %ignore NS::CLASS::CLASS(Internal*);
     %ignore NS::CLASS::CLASS(Internal&);
     %ignore NS::CLASS::operator=;
+    %ignore NS::CLASS::CLASS(CLASS &&);
 %enddef
 #else
 %define STANDARD_IGNORES(NS, CLASS)
@@ -62,6 +62,7 @@
     %ignore NS::CLASS::CLASS(Internal&);
     %ignore NS::CLASS::operator=;
     %ignore NS::CLASS::CLASS(const CLASS &);
+    %ignore NS::CLASS::CLASS(CLASS &&);
 %enddef
 #endif
 
@@ -76,6 +77,19 @@
 #define INC_OR_DEC(METHOD, OP, NS, CLASS, RET_TYPE) void METHOD() { OP(*self); }
 #endif
 
+#ifdef SWIGPERL
+/* In Perl, use inc() and dec() instead of next() and prev(). */
+#define NEXT_METHOD inc
+#define PREV_METHOD dec
+#define OPERATOR_EQ(NS, CLASS) bool equal(const NS::CLASS & o) const { return *self == o; }
+#define OPERATOR_NE(NS, CLASS) bool nequal(const NS::CLASS & o) const { return *self != o; }
+#else
+#define NEXT_METHOD next
+#define PREV_METHOD prev
+#define OPERATOR_EQ(NS, CLASS) bool equals(const NS::CLASS & o) const { return *self == o; }
+#define OPERATOR_NE(NS, CLASS)
+#endif
+
 /* For other languages, SWIG already renames operator() suitably. */
 #if defined SWIGGUILE || defined SWIGJAVA || defined SWIGPHP || defined SWIGTCL
 %rename(apply) *::operator();
@@ -92,9 +106,10 @@
     %ignore NS::CLASS::operator++;
     %ignore NS::CLASS::operator*;
     %extend NS::CLASS {
-	bool equals(const NS::CLASS & o) const { return *self == o; }
-	RET_TYPE DEREF_METHOD() const { return **self; }
-	INC_OR_DEC(next, ++, NS, CLASS, RET_TYPE)
+        OPERATOR_EQ(NS, CLASS)
+        OPERATOR_NE(NS, CLASS)
+        RET_TYPE DEREF_METHOD() const { return **self; }
+        INC_OR_DEC(NEXT_METHOD, ++, NS, CLASS, RET_TYPE)
     }
 %enddef
 
@@ -106,7 +121,7 @@
     %ignore NS::CLASS::operator+;
     %ignore NS::CLASS::operator-;
     %extend NS::CLASS {
-	INC_OR_DEC(prev, --, NS, CLASS, RET_TYPE)
+        INC_OR_DEC(PREV_METHOD, --, NS, CLASS, RET_TYPE)
     }
 %enddef
 
@@ -166,8 +181,8 @@ CONSTANT(int, Xapian, DB_NO_SYNC);
 CONSTANT(int, Xapian, DB_FULL_SYNC);
 CONSTANT(int, Xapian, DB_DANGEROUS);
 CONSTANT(int, Xapian, DB_NO_TERMLIST);
-CONSTANT(int, Xapian, DB_BACKEND_CHERT);
 CONSTANT(int, Xapian, DB_BACKEND_GLASS);
+CONSTANT(int, Xapian, DB_BACKEND_HONEY);
 CONSTANT(int, Xapian, DB_BACKEND_INMEMORY);
 CONSTANT(int, Xapian, DB_BACKEND_STUB);
 CONSTANT(int, Xapian, DB_RETRY_LOCK);
@@ -186,9 +201,6 @@ CONSTANT(int, Xapian, DOC_ASSUME_VALID);
  * them. */
 /* %include <xapian/error.h> */
 
-/* ErrorHandler isn't currently wrapped. */
-/* %include <xapian/errorhandler.h> */
-
 INPUT_ITERATOR_METHODS(Xapian, PositionIterator, Xapian::termpos, get_termpos)
 %include <xapian/positioniterator.h>
 
@@ -219,13 +231,11 @@ STANDARD_IGNORES(Xapian, Query)
 %ignore *::operator^=;
 %ignore *::operator*=;
 %ignore *::operator/=;
-#if defined SWIGCSHARP || defined SWIGGUILE || defined SWIGJAVA || defined SWIGLUA || defined SWIGPHP
 %ignore *::operator&;
 %ignore *::operator|;
 %ignore *::operator^;
 %ignore *::operator*;
 %ignore *::operator/;
-#endif
 %ignore Xapian::Query::LEAF_TERM;
 %ignore Xapian::Query::LEAF_POSTING_SOURCE;
 %ignore Xapian::Query::LEAF_MATCH_ALL;
@@ -241,8 +251,15 @@ STANDARD_IGNORES(Xapian, Query)
 #endif
 #ifndef XAPIAN_MIXED_SUBQUERIES_BY_ITERATOR_TYPEMAP
 %ignore Query(op op_, XapianSWIGQueryItor qbegin, XapianSWIGQueryItor qend,
-	      Xapian::termcount parameter = 0);
+              Xapian::termcount parameter = 0);
 #endif
+%ignore Xapian::Query::Query(const std::string&, Xapian::termcount = 1, Xapian::termpos = 0);
+%ignore Xapian::Query::Query(const char*, Xapian::termcount = 1, Xapian::termpos = 0);
+%extend Xapian::Query {
+    Query(op op_, std::string_view a, std::string_view b) {
+        return new Xapian::Query(op_, a, b);
+    }
+}
 %include <xapian/query.h>
 
 // Suppress warning that Xapian::Internal::intrusive_base is unknown.
@@ -258,12 +275,12 @@ STANDARD_IGNORES(Xapian, Stem)
 STANDARD_IGNORES(Xapian, TermGenerator)
 %ignore Xapian::TermGenerator::operator=;
 /* Ignore forms which use Utf8Iterator, as we don't wrap that class. */
-%ignore Xapian::TermGenerator::index_text(const Xapian::Utf8Iterator &);
-%ignore Xapian::TermGenerator::index_text(const Xapian::Utf8Iterator &, Xapian::termcount);
-%ignore Xapian::TermGenerator::index_text(const Xapian::Utf8Iterator &, Xapian::termcount, const std::string &);
-%ignore Xapian::TermGenerator::index_text_without_positions(const Xapian::Utf8Iterator &);
-%ignore Xapian::TermGenerator::index_text_without_positions(const Xapian::Utf8Iterator &, Xapian::termcount);
-%ignore Xapian::TermGenerator::index_text_without_positions(const Xapian::Utf8Iterator &, Xapian::termcount, const std::string &);
+%ignore Xapian::TermGenerator::index_text(const Xapian::Utf8Iterator&);
+%ignore Xapian::TermGenerator::index_text(const Xapian::Utf8Iterator&, Xapian::termcount);
+%ignore Xapian::TermGenerator::index_text(const Xapian::Utf8Iterator&, Xapian::termcount, std::string_view);
+%ignore Xapian::TermGenerator::index_text_without_positions(const Xapian::Utf8Iterator&);
+%ignore Xapian::TermGenerator::index_text_without_positions(const Xapian::Utf8Iterator&, Xapian::termcount);
+%ignore Xapian::TermGenerator::index_text_without_positions(const Xapian::Utf8Iterator&, Xapian::termcount, std::string_view);
 %ignore Xapian::TermGenerator::TermGenerator(const TermGenerator &);
 %include <xapian/termgenerator.h>
 
@@ -276,19 +293,19 @@ STANDARD_IGNORES(Xapian, MSet)
 #endif
 %extend Xapian::MSet {
     Xapian::docid get_docid(Xapian::doccount i) const {
-	return *(*self)[i];
+        return *(*self)[i];
     }
 
     Xapian::Document get_document(Xapian::doccount i) const {
-	return (*self)[i].get_document();
+        return (*self)[i].get_document();
     }
 
     Xapian::MSetIterator get_hit(Xapian::doccount i) const {
-	return (*self)[i];
+        return (*self)[i];
     }
 
     int get_document_percentage(Xapian::doccount i) const {
-	return self->convert_to_percent((*self)[i]);
+        return self->convert_to_percent((*self)[i]);
     }
 }
 
@@ -305,10 +322,14 @@ RANDOM_ACCESS_ITERATOR_METHODS(Xapian, ESetIterator, std::string, get_term)
 
 STANDARD_IGNORES(Xapian, RSet)
 
-STANDARD_IGNORES(Xapian, Enquire)
+%include <xapian/rset.h>
 
 SUBCLASSABLE(Xapian, MatchDecider)
 
+%include <xapian/matchdecider.h>
+
+STANDARD_IGNORES(Xapian, Enquire)
+
 #ifdef XAPIAN_TERMITERATOR_PAIR_OUTPUT_TYPEMAP
 /* Instantiating the template we're going to use avoids SWIG wrapping uses
  * of it in SwigValueWrapper.
@@ -321,16 +342,12 @@ SUBCLASSABLE(Xapian, MatchDecider)
      */
     std::pair<Xapian::TermIterator, Xapian::TermIterator>
     get_matching_terms(const Xapian::MSetIterator & item) const {
-	return std::make_pair($self->get_matching_terms_begin(item),
-			      $self->get_matching_terms_end(item));
+        return std::make_pair($self->get_matching_terms_begin(item),
+                              $self->get_matching_terms_end(item));
     }
 }
 #endif
 
-/* We don't wrap ErrorHandler, so ignore the optional ErrorHandler parameter.
- */
-%ignore Enquire(const Database &, ErrorHandler *);
-
 %include <xapian/enquire.h>
 
 SUBCLASSABLE(Xapian, ExpandDecider)
@@ -351,12 +368,12 @@ SUBCLASSABLE(Xapian, KeyMaker)
 %extend Xapian::SimpleStopper {
     /** Load stop words from a text file (one word per line). */
     SimpleStopper(const std::string &file) {
-	ifstream in_file(file.c_str());
-	if (!in_file.is_open())
-	    throw Xapian::InvalidArgumentError("Stopword file not found: " + file);
-	istream_iterator<std::string> in_iter(in_file);
-	istream_iterator<std::string> eof;
-	return new Xapian::SimpleStopper(in_iter, eof);
+        ifstream in_file(file.c_str());
+        if (!in_file.is_open())
+            throw Xapian::InvalidArgumentError("Stopword file not found: " + file);
+        istream_iterator<std::string> in_iter(in_file);
+        istream_iterator<std::string> eof;
+        return new Xapian::SimpleStopper(in_iter, eof);
     }
 }
 
@@ -365,10 +382,8 @@ SUBCLASSABLE(Xapian, FieldProcessor)
 %warnfilter(SWIGWARN_TYPE_UNDEFINED_CLASS) Xapian::Stopper;
 SUBCLASSABLE(Xapian, RangeProcessor)
 SUBCLASSABLE(Xapian, Stopper)
-SUBCLASSABLE(Xapian, ValueRangeProcessor)
 // Suppress warning that Xapian::Internal::opt_intrusive_base is unknown.
 %warnfilter(SWIGWARN_TYPE_UNDEFINED_CLASS) Xapian::RangeProcessor;
-%warnfilter(SWIGWARN_TYPE_UNDEFINED_CLASS) Xapian::ValueRangeProcessor;
 %warnfilter(SWIGWARN_TYPE_UNDEFINED_CLASS) Xapian::FieldProcessor;
 STANDARD_IGNORES(Xapian, QueryParser)
 %ignore Xapian::QueryParser::QueryParser(const QueryParser &);
@@ -423,45 +438,55 @@ INPUT_ITERATOR_METHODS(Xapian, LatLongCoordsIterator, LatLongCoord, get_coord)
 STANDARD_IGNORES(Xapian, Database)
 STANDARD_IGNORES(Xapian, WritableDatabase)
 %ignore Xapian::WritableDatabase::WritableDatabase(Database::Internal *);
-%ignore Xapian::Database::check(const std::string &, int, std::ostream *);
-%ignore Xapian::Database::check(int fd, int, std::ostream *);
+%ignore Xapian::Database::check(std::string_view, int, std::ostream*);
+%ignore Xapian::Database::check(int fd, int, std::ostream*);
 %include <xapian/database.h>
 %extend Xapian::Database {
-    static size_t check(const std::string &path, int opts = 0) {
-	return Xapian::Database::check(path, opts, opts ? &std::cout : NULL);
+    static size_t check(std::string_view path, int opts = 0) {
+        return Xapian::Database::check(path, opts, opts ? &std::cout : NULL);
     }
 }
 
 #if defined SWIGCSHARP || defined SWIGJAVA
 
-/* xapian/dbfactory.h is currently wrapped via fake class declarations in
- * fake_dbfactory.i for C# and Java. */
-
-#else
-
-#ifdef XAPIAN_BINDINGS_SKIP_DEPRECATED_DB_FACTORIES
-%ignore Xapian::InMemory::open;
-%ignore Xapian::Chert::open;
-%ignore Xapian::Auto::open_stub;
-#else
-
-%rename("inmemory_open") Xapian::InMemory::open;
-
-/* SWIG Tcl wrappers don't call destructors for classes returned by factory
- * functions, so we don't wrap them so users are forced to use the
- * WritableDatabase ctor instead. */
-#ifdef SWIGTCL
-%ignore Xapian::Chert::open(const std::string &dir, int action, int block_size = 8192);
-#endif
+/* C# and Java don't allow functions outside a class so we can't use SWIG's
+ * %nspace feature here.  Instead we pretend to SWIG that the C++
+ * Xapian::Remote namespace is actually a Xapian::Remote class with public
+ * static functions.  The code SWIG generates will work fine, and we get
+ * xapian.Remote.open() in Java and Xapian.Remote.open() in C#.
+ */
 
-%rename("chert_open") Xapian::Chert::open;
+namespace Xapian {
+
+class Remote {
+    // Private constructor and destructor so SWIG doesn't try to call them.
+    Remote();
+    ~Remote();
+  public:
+    static Database open(const std::string &host,
+                         unsigned int port,
+                         unsigned timeout = 10000,
+                         unsigned connect_timeout = 10000);
+
+    static WritableDatabase open_writable(const std::string &host,
+                                          unsigned int port,
+                                          unsigned timeout = 0,
+                                          unsigned connect_timeout = 10000,
+                                          int flags = 0);
+
+    static Database open(const std::string &program,
+                         const std::string &args,
+                         unsigned timeout = 10000);
+
+    static WritableDatabase open_writable(const std::string &program,
+                                          const std::string &args,
+                                          unsigned timeout = 0,
+                                          int flags = 0);
+};
 
-#ifndef SWIGPHP
-/* PHP renames this to auto_open_stub() in php/php.i. */
-%rename("open_stub") Xapian::Auto::open_stub;
-#endif
+}
 
-#endif
+#else
 
 %rename("remote_open") Xapian::Remote::open;
 %rename("remote_open_writable") Xapian::Remote::open_writable;