From e4bac33eee3f8cd36ef451c83cb5b2d3b9d7e10d Mon Sep 17 00:00:00 2001 From: rurban Date: Sat, 27 Nov 2004 14:39:05 +0000 Subject: [PATCH] simpified regex search architecture: no db specific node methods anymore, new sql() method for each node parallel to regexp() (which returns pcre) regex types bitmasked (op's not yet) new regex=sql clarified WikiDB::quote() backend methods: ->quote() adds surrounsing quotes ->qstr() (new method) assumes strings and adds no quotes! (in contrast to ADODB) pear and adodb have now unified quote methods for all generic queries. git-svn-id: svn://svn.code.sf.net/p/phpwiki/code/trunk@4198 96ab9672-09ca-45d6-a79d-3d69d39ca109 --- lib/DbSession.php | 9 +- lib/Request.php | 11 +- lib/TextSearchQuery.php | 230 ++++++++++++++++------------ lib/WikiDB/ADODB.php | 9 +- lib/WikiDB/SQL.php | 12 +- lib/WikiDB/backend.php | 11 +- lib/WikiDB/backend/ADODB.php | 23 +-- lib/WikiDB/backend/PearDB.php | 33 ++-- lib/WikiDB/backend/PearDB_mysql.php | 16 +- lib/WikiDB/backend/PearDB_oci8.php | 9 +- lib/WikiDB/backend/PearDB_pgsql.php | 27 ++-- lib/WikiGroup.php | 10 +- lib/plugin/TitleSearch.php | 34 ++-- 13 files changed, 238 insertions(+), 196 deletions(-) diff --git a/lib/DbSession.php b/lib/DbSession.php index d1545f27e..28db3b9c2 100644 --- a/lib/DbSession.php +++ b/lib/DbSession.php @@ -1,4 +1,4 @@ -_backend->query($sql); } - function quote($string) { - return $this->_backend->quote($string); - } - + function quote($string) { return $string; } } class DbSession_SQL @@ -98,7 +95,7 @@ extends DbSession function query($sql) { return $this->_dbh->query($sql); } - + // adds surrounding quotes function quote($string) { return $this->_dbh->quote($string); } diff --git a/lib/Request.php b/lib/Request.php index 6382dfbce..8ff746eee 100644 --- a/lib/Request.php +++ b/lib/Request.php @@ -1,5 +1,5 @@ _dbi)) { // check same hosts in referer and request and remove them - $ext_where = " AND LEFT(referer,$blen) <> '$base'" - ." AND LEFT(referer,$blen) <> LEFT(CONCAT('".SERVER_URL."',request_uri),$blen)"; + $ext_where = " AND LEFT(referer,$blen) <> ".$this->_dbi->quote($base) + ." AND LEFT(referer,$blen) <> LEFT(CONCAT(".$this->_dbi->quote(SERVER_URL).",request_uri),$blen)"; return $this->_read_sql_query("(referer <>'' AND NOT(ISNULL(referer)))" .($external_only ? $ext_where : '') ." ORDER BY time_stamp DESC" @@ -859,7 +859,7 @@ class Request_AccessLog { function get_host($host, $since_minutes=20) { if ($this->logtable) { // mysql specific only: - return $this->read_sql("request_host=".$dbh->quote($host)." AND time_stamp > ". (time()-$since_minutes*60) + return $this->read_sql("request_host=".$this->_dbi->quote($host)." AND time_stamp > ". (time()-$since_minutes*60) ." ORDER BY time_stamp DESC"); } else { $iter = new WikiDB_Array_generic_iter(); @@ -1305,6 +1305,9 @@ class HTTP_ValidatorSet { // $Log: not supported by cvs2svn $ +// Revision 1.80 2004/11/21 11:59:16 rurban +// remove final \n to be ob_cache independent +// // Revision 1.79 2004/11/11 18:29:44 rurban // (write_sql) isOpen really is useless in non-SQL, do more explicit check // diff --git a/lib/TextSearchQuery.php b/lib/TextSearchQuery.php index fd093bbde..b9d7eb4ee 100644 --- a/lib/TextSearchQuery.php +++ b/lib/TextSearchQuery.php @@ -1,4 +1,4 @@ - posix-style, "/word/" => pcre-style * or use regex='glob' to use file wildcard-like matching. (not yet) * - * The parseed tree is then converted to the needed PCRE (highlight, simple backends) - * or SQL functions. + * The parsed tree is then converted to the needed PCRE (highlight, simple backends) + * or SQL functions. * * @author: Jeff Dairiki - * @author: Reini Urban (case and regex, enhanced sql callbacks) + * @author: Reini Urban (case and regex detection, enhanced sql callbacks) */ + +// regex-style: 'auto', 'none', 'glob', 'posix', 'pcre', 'sql' +define ('TSQ_REGEX_NONE', 0); +define ('TSQ_REGEX_AUTO', 1); +define ('TSQ_REGEX_POSIX', 2); +define ('TSQ_REGEX_GLOB', 4); +define ('TSQ_REGEX_PCRE', 8); +define ('TSQ_REGEX_SQL', 16); + class TextSearchQuery { /** * Create a new query. * * @param $search_query string The query. Syntax is as described above. * Note that an empty $search_query will match anything. + * @param $case_exact boolean + * @param $regex string one of 'auto', 'none', 'glob', 'posix', 'pcre', 'sql' * @see TextSearchQuery - * TODO: support $regex arg, try to detect regex from $search_query (glob-style) */ function TextSearchQuery($search_query, $case_exact=false, $regex='auto') { - $parser = new TextSearchQuery_Parser; - $this->_isregex = $regex; // default: auto + if ($regex == 'none' or !$regex) + $this->_regex = 0; + elseif (defined("TSQ_REGEX_".strtoupper($regex))) + $this->_regex = constant("TSQ_REGEX_".strtoupper($regex)); + else { + trigger_error(fmt("Unsupported argument: %s=%s", 'regex', $regex)); + $this->_regex = 0; + } $this->_case_exact = $case_exact; - $this->_tree = $parser->parse($search_query, $case_exact, $regex); + $parser = new TextSearchQuery_Parser; + $this->_tree = $parser->parse($search_query, $case_exact, $this->_regex); $this->_optimize(); } @@ -76,7 +93,7 @@ class TextSearchQuery { */ function asRegexp() { if (!isset($this->_regexp)) { - if ($this->_isregex) // TODO: convert glob-style regex to pcre + if ($this->_regex) $this->_regexp = '/' . $this->_tree->regexp() . '/'.($this->_case_exact?'':'i').'sS'; else $this->_regexp = '/^' . $this->_tree->regexp() . '/'.($this->_case_exact?'':'i').'sS'; @@ -93,6 +110,7 @@ class TextSearchQuery { function match($string) { return preg_match($this->asRegexp(), $string); } + /** * Get a regular expression suitable for highlighting matched words. @@ -107,8 +125,7 @@ class TextSearchQuery { $words = array_unique($this->_tree->highlight_words()); if (!$words) { $this->_hilight_regexp = false; - } - else { + } else { foreach ($words as $key => $word) $words[$key] = preg_quote($word, '/'); $this->_hilight_regexp = '(?:' . join('|', $words) . ')'; @@ -118,7 +135,7 @@ class TextSearchQuery { } /** - * Make an SQL clause which matches the query. + * Make an SQL clause which matches the query. (deprecated, use makeSqlClause instead) * * @param $make_sql_clause_cb WikiCallback * A callback which takes a single word as an argument and @@ -129,9 +146,8 @@ class TextSearchQuery { * TODO: support db-specific extensions, like MATCH AGAINST or REGEX * mysql => 4.0.1 can also do Google: MATCH AGAINST IN BOOLEAN MODE * How? WikiDB backend method? - * Case-sensitivity option. * - * Example usage: + * Old example usage: *
      *     function sql_title_match($word) {
      *         return sprintf("LOWER(title) like '%s'",
@@ -147,30 +163,15 @@ class TextSearchQuery {
      * This will result in $sql_clause containing something like
      * "(LOWER(title) like 'wiki') AND NOT (LOWER(title) like 'page')".
      *
-     * @return string The PCRE regexp.
+     * @return string The SQL clause.
      */
     function makeSqlClause($sql_clause_cb) {
-        $this->_sql_clause_cb = $make_sql_clause_cb;
+        $this->_sql_clause_cb = $sql_clause_cb;
         return $this->_sql_clause($this->_tree);
     }
-
-    // get away with the callback and use a db-specific search class instead.
-    // "WikiDB_backend_PearDB_search"
-    // methods named as the op's.
-    function makeSqlClauseObj(&$sql_search_cb) {
-        $this->_sql_clause_cb = $sql_search_cb;
-        return $this->_sql_clause_obj($this->_tree);
-    }
-
+    // deprecated: use _sql_clause_obj now.
     function _sql_clause($node) {
         switch ($node->op) {
-/*      case 'EXACT':       // word => word
-            return $this->_sql_clause_cb->call($node->word);
-        case 'STARTS_WITH': // word => word%
-            return $this->_sql_clause_cb->call($node->word);
-        case 'ENDS_WITH':   // word => %word
-            return $this->_sql_clause_cb->call($node->word);
-*/
         case 'WORD':        // word => %word%
             return $this->_sql_clause_cb->call($node->word);
         case 'NOT':
@@ -187,6 +188,14 @@ class TextSearchQuery {
         }
     }
 
+    /** Get away with the callback and use a db-specific search class instead.
+     * @see WikiDB_backend_PearDB_search
+     */
+    function makeSqlClauseObj(&$sql_search_cb) {
+        $this->_sql_clause_cb = $sql_search_cb;
+        return $this->_sql_clause_obj($this->_tree);
+    }
+
     function _sql_clause_obj($node) {
         switch ($node->op) {
         case 'NOT':
@@ -287,55 +296,74 @@ class TextSearchQuery_node
 }
 
 /**
- * A whitespace seperated word.
+ * A word.
  */
 class TextSearchQuery_node_word
-extends TextSearchQuery_node {
+extends TextSearchQuery_node
+{
     var $op = "WORD";
     
     function TextSearchQuery_node_word($word) {
         $this->word = $word;
     }
-
     function regexp() {
         return '(?=.*' . preg_quote($this->word, '/') . ')';
     }
-
     function highlight_words($negated = false) {
         return $negated ? array() : array($this->word);
     }
+    function _sql_quote() {
+        $word = preg_replace('/(?=[%_\\\\])/', "\\", $this->word);
+        return $GLOBALS['request']->_dbi->qstr($word);
+    }
+    function sql()    { return '%'.$this->_sql_quote($this->word).'%'; }
 }
 
 class TextSearchQuery_node_starts_with
 extends TextSearchQuery_node_word {
     var $op = "STARTS_WITH";
     function regexp() { return '(?=' . preg_quote($this->word, '/') . ')'; }
+    function sql()    { return $this->_sql_quote($this->word).'%'; }
 }
+
 class TextSearchQuery_node_ends_with
 extends TextSearchQuery_node_word {
     var $op = "ENDS_WITH";
     function regexp() { return '(?=' . preg_quote($this->word, '/') . '.*)'; }
+    function sql()    { return '%'.$this->_sql_quote($this->word); }
 }
+
 class TextSearchQuery_node_exact
 extends TextSearchQuery_node_word {
     var $op = "EXACT";
     function regexp() { return '(?=\B' . preg_quote($this->word, '/') . '\b)'; }
+    function sql()    { return $this->_sql_squote($this->word); }
 }
-class TextSearchQuery_node_glob
+
+class TextSearchQuery_node_regex // posix regex. FIXME!
 extends TextSearchQuery_node_word {
-    var $op = "REGEX";
-    function regexp() { return '(?=\B' . preg_quote(glob_to_pcre($this->word), '/') . '\b)'; }
+    var $op = "REGEX"; // using REGEXP or ~ extension
+    function regexp() { return '(?=\B' . $this->word . '\b)'; }
+    function sql()    { return $this->_sql_quote($this->word); }
 }
-class TextSearchQuery_node_regex
-extends TextSearchQuery_node_word {
-    var $op = "REGEX";
-    function regexp() { return '(?=\B' . preg_quote($this->word, '/') . '\b)'; }
+
+class TextSearchQuery_node_regex_glob
+extends TextSearchQuery_node_regex {
+    var $op = "REGEX_GLOB";
+    function regexp() { return '(?=\B' . glob_to_pcre($this->word) . '\b)'; }
 }
-// FIXME for SQL
-class TextSearchQuery_node_pcre
-extends TextSearchQuery_node_word {
-    var $op = "REGEX";
-    function regexp() { return '(?=\B' . preg_quote($this->word, '/') . '\b)'; }
+
+class TextSearchQuery_node_regex_pcre // how to handle pcre modifiers? /i
+extends TextSearchQuery_node_regex {
+    var $op = "REGEX_PCRE";
+    function regexp() { return $this->word; }
+}
+
+class TextSearchQuery_node_regex_sql
+extends TextSearchQuery_node_regex {
+    var $op = "REGEX_SQL"; // using LIKE
+    function regexp() { return str_replace(array("/%/","/_/"), array(".*","."), $this->word); }
+    function sql()    { return $this->word; }
 }
 
 /**
@@ -500,17 +528,20 @@ extends TextSearchQuery_node_binop
 //   op's (and, or, not) are forced to lowercase in the tokenizer.
 //
 ////////////////////////////////////////////////////////////////
-define ('TSQ_TOK_WORD',   1);
-define ('TSQ_TOK_BINOP',  2);
-define ('TSQ_TOK_NOT',    4);
-define ('TSQ_TOK_LPAREN', 8);
-define ('TSQ_TOK_RPAREN', 16);
+define ('TSQ_TOK_BINOP',  1);
+define ('TSQ_TOK_NOT',    2);
+define ('TSQ_TOK_LPAREN', 4);
+define ('TSQ_TOK_RPAREN', 8);
+define ('TSQ_TOK_WORD',   16);
 define ('TSQ_TOK_STARTS_WITH', 32);
 define ('TSQ_TOK_ENDS_WITH', 64);
 define ('TSQ_TOK_EXACT', 128);
-define ('TSQ_TOK_REGEX_POSIX', 256);
-define ('TSQ_TOK_REGEX_PCRE', 512);
-define ('TSQ_TOK_REGEX_GLOB', 1024);
+define ('TSQ_TOK_REGEX', 256);
+define ('TSQ_TOK_REGEX_GLOB', 512);
+define ('TSQ_TOK_REGEX_PCRE', 1024);
+define ('TSQ_TOK_REGEX_SQL', 2048);
+// all bits from word to the last.
+define ('TSQ_ALLWORDS', (2048*2)-1 - (16-1));
 
 class TextSearchQuery_Parser 
 {
@@ -545,16 +576,16 @@ class TextSearchQuery_Parser
      * /[^-()\s][^()\s]*  WORD
      * /"[^"]*"/	  WORD
      * /'[^']*'/	  WORD
+     *
      * ^WORD              STARTS_WITH
      * WORD*              STARTS_WITH
      * *WORD              ENDS_WITH
      * ^WORD$             EXACT
-     * /regex/            PCRE-style REGEX
-     * re:WORD            POSIX-style REGEX
      */
 
-    function parse ($search_expr, $case_exact=false, $regex='auto') {
+    function parse ($search_expr, $case_exact=false, $regex=TSQ_REGEX_AUTO) {
         $this->lexer = new TextSearchQuery_Lexer($search_expr, $case_exact, $regex);
+        $this->_regex = $regex;
         $tree = $this->get_list('toplevel');
         assert($this->lexer->eof());
         unset($this->lexer);
@@ -572,7 +603,6 @@ class TextSearchQuery_Parser
         
         while ( ($expr = $this->get_expr())
                 || ($expr = $this->get_word($accept_as_words)) ) {
-            
             $list[] = $expr;
         }
 
@@ -611,9 +641,7 @@ class TextSearchQuery_Parser
     
 
     function get_atom() {
-        if ($word = $this->get_word(TSQ_TOK_WORD + TSQ_TOK_STARTS_WITH + TSQ_TOK_ENDS_WITH 
-        			   + TSQ_TOK_EXACT + TSQ_TOK_REGEX_GLOB + TSQ_TOK_REGEX_PCRE
-        			   + TSQ_TOK_REGEX_POSIX))
+        if ($word = $this->get_word(TSQ_ALLWORDS))
             return $word;
 
         $savedpos = $this->lexer->tell();
@@ -629,28 +657,21 @@ class TextSearchQuery_Parser
         return false;
     }
 
-    function get_word($accept = TSQ_TOK_WORD) {
-        if ( $accept & TSQ_TOK_WORD and ($word = $this->lexer->get(TSQ_TOK_WORD)) )
-            return new TextSearchQuery_node_word($word);
-        if ( $accept & TSQ_TOK_STARTS_WITH and ($word = $this->lexer->get(TSQ_TOK_STARTS_WITH)) )
-            return new TextSearchQuery_node_starts_with($word);
-        if ( $accept & TSQ_TOK_ENDS_WITH and ($word = $this->lexer->get(TSQ_TOK_ENDS_WITH)) )
-            return new TextSearchQuery_node_ends_with($word);
-        if ( $accept & TSQ_TOK_EXACT and ($word = $this->lexer->get(TSQ_TOK_EXACT)) )
-            return new TextSearchQuery_node_exact($word);
-        if ( $accept & TSQ_TOK_REGEX_GLOB and ($word = $this->lexer->get(TSQ_TOK_REGEX_GLOB)) )
-            return new TextSearchQuery_node_glob($word);
-        if ( $accept & TSQ_TOK_REGEX_POSIX and ($word = $this->lexer->get(TSQ_TOK_REGEX_POSIX)) )
-            return new TextSearchQuery_node_regex($word);
-        if ( $accept & TSQ_TOK_REGEX_PCRE and ($word = $this->lexer->get(TSQ_TOK_REGEX_PCRE)) )
-            return new TextSearchQuery_node_pcre($word);
+    function get_word($accept = TSQ_ALLWORDS) {
+        foreach (array("WORD","STARTS_WITH","ENDS_WITH","EXACT",
+                       "REGEX","REGEX_GLOB","REGEX_PCRE") as $tok) {
+            $const = constant("TSQ_TOK_".$tok);
+            if ( $accept & $const and ($word = $this->lexer->get($const)) ) {
+                $classname = "TextSearchQuery_node_".strtolower($tok);
+                return new $classname($word);
+            }
+        }
         return false;
     }
 }
 
-//TODO: support glob-style regex: $regex='glob'
 class TextSearchQuery_Lexer {
-    function TextSearchQuery_Lexer ($query_str, $case_exact=false, $regex='auto') {
+    function TextSearchQuery_Lexer ($query_str, $case_exact=false, $regex=TSQ_REGEX_AUTO) {
         $this->tokens = $this->tokenize($query_str, $case_exact, $regex);
         $this->pos = 0;
     }
@@ -666,8 +687,12 @@ class TextSearchQuery_Lexer {
     function eof() {
         return $this->pos == count($this->tokens);
     }
-
-    function tokenize($string, $case_exact=false, $regex='auto') {
+    
+    /**
+     * TODO: support more regex styles, esp. prefer the forced ones over auto
+     * re: and // stuff
+     */
+    function tokenize($string, $case_exact=false, $regex=TSQ_REGEX_AUTO) {
         $tokens = array();
         $buf = $case_exact ? ltrim($string) : strtolower(ltrim($string));
         while (!empty($buf)) {
@@ -683,40 +708,41 @@ class TextSearchQuery_Lexer {
                 $val = $m[1];
                 $type = $m[1] == '(' ? TSQ_TOK_LPAREN : TSQ_TOK_RPAREN;
             }
-            elseif (preg_match('/^re:([^-()][^()\s]*)\s*/', $buf, $m)) {
-            	$regex = true; // posix-style
-                $val = $m[1];
-                $type = TSQ_TOK_REGEX_POSIX;
-            }
-            elseif (preg_match('/^\/([^-()][^()\s]*)\/\s*/', $buf, $m)) {
-            	$regex = true; // pcre-style
-                $val = $m[1];
-                $type = TSQ_TOK_REGEX_PCRE;
-            }
-            elseif ($regex and preg_match('/^\^([^-()][^()\s]*)\$\s*/', $buf, $m)) {
-                $val = $m[1];
-                $type = TSQ_TOK_EXACT;
-            }
-            elseif ($regex and preg_match('/^\^([^-()][^()\s]*)\s*/', $buf, $m)) {
+            // ^word
+            elseif ($regex & (TSQ_REGEX_AUTO|TSQ_REGEX_POSIX|TSQ_REGEX_PCRE)
+                    and preg_match('/^\^([^-()][^()\s]*)\s*/', $buf, $m)) {
                 $val = $m[1];
                 $type = TSQ_TOK_STARTS_WITH;
             }
-            elseif ($regex and preg_match('/^([^-()][^()\s]*)\*\s*/', $buf, $m)) {
+            // word*
+            elseif ($regex & (TSQ_REGEX_AUTO|TSQ_REGEX_POSIX|TSQ_REGEX_PCRE)
+                    and preg_match('/^([^-()][^()\s]*)\*\s*/', $buf, $m)) {
                 $val = $m[1];
                 $type = TSQ_TOK_STARTS_WITH;
             }
-            elseif ($regex and preg_match('/^\*([^-()][^()\s]*)\s*/', $buf, $m)) {
+            // *word
+            elseif ($regex & (TSQ_REGEX_AUTO|TSQ_REGEX_POSIX|TSQ_REGEX_PCRE)
+                    and preg_match('/^\*([^-()][^()\s]*)\s*/', $buf, $m)) {
                 $val = $m[1];
                 $type = TSQ_TOK_ENDS_WITH;
             }
+            // ^word$
+            elseif ($regex & (TSQ_REGEX_AUTO|TSQ_REGEX_POSIX|TSQ_REGEX_PCRE)
+                    and preg_match('/^\^([^-()][^()\s]*)\$\s*/', $buf, $m)) {
+                $val = $m[1];
+                $type = TSQ_TOK_EXACT;
+            }
+            // "words "
             elseif (preg_match('/^ " ( (?: [^"]+ | "" )* ) " \s*/x', $buf, $m)) {
                 $val = str_replace('""', '"', $m[1]);
                 $type = TSQ_TOK_WORD;
             }
+            // 'words '
             elseif (preg_match("/^ ' ( (?:[^']+|'')* ) ' \s*/x", $buf, $m)) {
                 $val = str_replace("''", "'", $m[1]);
                 $type = TSQ_TOK_WORD;
             }
+            // word
             elseif (preg_match('/^([^-()][^()\s]*)\s*/', $buf, $m)) {
                 $val = $m[1];
                 $type = TSQ_TOK_WORD;
@@ -727,8 +753,7 @@ class TextSearchQuery_Lexer {
             }
             $buf = substr($buf, strlen($m[0]));
 
-            /* refine the simple parsing from above: bla*bla, bla?bla
-             
+            /* refine the simple parsing from above: bla*bla, bla?bla, ...
             if ($regex and $type == TSQ_TOK_WORD) {
             	if (substr($val,0,1) == "^")
             	    $type = TSQ_TOK_STARTS_WITH;
@@ -736,7 +761,8 @@ class TextSearchQuery_Lexer {
             	    $type = TSQ_TOK_ENDS_WITH;
             	elseif (substr($val,-1,1) == "*")
             	    $type = TSQ_TOK_STARTS_WITH;
-            } */
+            }
+            */
             $tokens[] = array($type, $val);
         }
         return $tokens;
diff --git a/lib/WikiDB/ADODB.php b/lib/WikiDB/ADODB.php
index dc6f3b5fb..c02b2313a 100644
--- a/lib/WikiDB/ADODB.php
+++ b/lib/WikiDB/ADODB.php
@@ -1,5 +1,5 @@
 _cache->_id_cache[$pagename];
     }
 
-    // ADODB handles everything as string
+    // add surrounding quotes '' if string
     function quote ($in) {
         if (is_int($in) || is_double($in)) {
             return $in;
@@ -56,6 +56,11 @@ class WikiDB_ADODB extends WikiDB
             return $this->_backend->_dbh->qstr($in);
         }
     }
+    // ADODB handles everything as string
+    // Don't add surrounding quotes '', same as in PearDB
+    function qstr ($in) {
+        return $this->_backend->_dbh->addq($in);
+    }
 
     function isOpen () {
         global $request;
diff --git a/lib/WikiDB/SQL.php b/lib/WikiDB/SQL.php
index e6a2c2acd..c84217c28 100644
--- a/lib/WikiDB/SQL.php
+++ b/lib/WikiDB/SQL.php
@@ -1,4 +1,4 @@
-_cache->_id_cache[$pagename];
     }
 
-    // return with surrounding quotes as ADODB!
-    function quote ($s) {
-        return $this->_backend->_dbh->quoteSmart($s);
-    }
-    
+    // adds surrounding quotes 
+    function quote ($s) { return $this->_backend->_dbh->quoteSmart($s); }
+    // no surrounding quotes because we know it's a string
+    function qstr ($s) {  return $this->_backend->_dbh->escapeSimple($s); }
+
     function isOpen () {
         global $request;
         if (!$request->_dbi) return false;
diff --git a/lib/WikiDB/backend.php b/lib/WikiDB/backend.php
index f45d0988d..c4042ae44 100644
--- a/lib/WikiDB/backend.php
+++ b/lib/WikiDB/backend.php
@@ -1,5 +1,5 @@
 page_tbl_fields;
         $field_list = $this->page_tbl_field_list;
-        $searchobj = new WikiDB_backend_PearDB_search($search, $dbh);
+        $searchobj = new WikiDB_backend_ADODB_search($search, $dbh);
         
         if ($fullsearch) {
             $table .= ", $recent_tbl";
@@ -1105,26 +1105,14 @@ extends WikiDB_backend_search
         $this->_dbh =& $dbh;
         $this->_case_exact = $search->_case_exact;
     }
-    function _quote($word) {
-        $word = preg_replace('/(?=[%_\\\\])/', "\\", $word);
-        return $this->_dbh->addq($this->_case_exact ? $word : strtolower($word)); // without '...'
-    }
-    function EXACT($word) { return $this->_quote($word); }
-    function STARTS_WITH($word) { return $this->_quote($word)."%"; }
-    function ENDS_WITH($word) { return "%".$this->_quote($word); }
-    function WORD($word) { return "%".$this->_quote($word)."%"; }
-    function REGEX($word) { return $this->_quote($word); }
-
     function _pagename_match_clause($node) {
-        $method = $node->op;
-        $word = $this->$method($node->word);
+        $word = $node->sql($word);
         return $this->_case_exact 
             ? "pagename LIKE '$word'"
             : "LOWER(pagename) LIKE '$word'";
     }
     function _fulltext_match_clause($node) { 
-        $method = $node->op;
-        $word = $this->$method($node->word);
+        $word = $node->sql($word);
         return $this->_case_exact
             ? "pagename LIKE '$word' OR content LIKE '$word'"
             : "LOWER(pagename) LIKE '$word' OR content LIKE '$word'";
@@ -1291,6 +1279,9 @@ extends WikiDB_backend_search
     }
 
 // $Log: not supported by cvs2svn $
+// Revision 1.58  2004/11/26 18:39:02  rurban
+// new regex search parser and SQL backends (90% complete, glob and pcre backends missing)
+//
 // Revision 1.57  2004/11/25 17:20:51  rurban
 // and again a couple of more native db args: backlinks
 //
diff --git a/lib/WikiDB/backend/PearDB.php b/lib/WikiDB/backend/PearDB.php
index 39473afba..8149920c5 100644
--- a/lib/WikiDB/backend/PearDB.php
+++ b/lib/WikiDB/backend/PearDB.php
@@ -1,5 +1,5 @@
 _table_names);
 
         $searchclass = get_class($this)."_search";
-        if (!class_exists($searchclass)) // no need to define it everywhere and then fallback. memory
+        // no need to define it everywhere and then fallback. memory!
+        if (!class_exists($searchclass))
             $searchclass = "WikiDB_backend_PearDB_search";
         $searchobj = new $searchclass($search, $dbh);
         
@@ -1125,37 +1126,18 @@ extends WikiDB_backend_search
         $this->_dbh = $dbh;
         $this->_case_exact = $search->_case_exact;
     }
-    function _quote($word) {
-        $word = preg_replace('/(?=[%_\\\\])/', "\\", $word);
-        return $this->_dbh->escapeSimple($this->_case_exact ? $word : strtolower($word));
-    }
-    function EXACT($word) { return $this->_quote($word); }
-    function STARTS_WITH($word) { return $this->_quote($word)."%"; }
-    function ENDS_WITH($word) { return "%".$this->_quote($word); }
-    function WORD($word) { return "%".$this->_quote($word)."%"; } // substring
-    function REGEX($word) { // posix regex
-        // we really must know if the backend supports posix REGEXP 
-        // or if it has to be converted to SQL matching. * => %, ? => _
-    	return $this->_dbh->escapeSimple($word);
-    }
-
     function _pagename_match_clause($node) { 
-        $method = $node->op;
-        $word = $this->$method($node->word);
-        if ($method == 'REGEX') { // posix regex extensions
+        $word = $node->sql();
+        if ($node->op == 'REGEX') { // posix regex extensions
             if (preg_match("/mysql/i", $this->_dbh->phptype))
                 return "pagename REGEXP '$word'";
-            elseif (preg_match("/pgsql/i", $this->_dbh->phptype))
-                return $this->_case_exact ? "pagename ~* '$word'"
-                                          : "pagename ~ '$word'";
         } else {
             return $this->_case_exact ? "pagename LIKE '$word'" 
                                       : "LOWER(pagename) LIKE '$word'";
         }
     }
     function _fulltext_match_clause($node) { 
-        $method = $node->op;
-        $word = $this->WORD($node->word);
+        $word = $node->sql();
         return $this->_pagename_match_clause($node)
                // probably convert this MATCH AGAINST or SUBSTR/POSITION without wildcards
                . ($this->_case_exact ? " OR content LIKE '$word'" 
@@ -1164,6 +1146,9 @@ extends WikiDB_backend_search
 }
 
 // $Log: not supported by cvs2svn $
+// Revision 1.73  2004/11/26 18:39:02  rurban
+// new regex search parser and SQL backends (90% complete, glob and pcre backends missing)
+//
 // Revision 1.72  2004/11/25 17:20:51  rurban
 // and again a couple of more native db args: backlinks
 //
diff --git a/lib/WikiDB/backend/PearDB_mysql.php b/lib/WikiDB/backend/PearDB_mysql.php
index 43316dea2..c25d20ffe 100644
--- a/lib/WikiDB/backend/PearDB_mysql.php
+++ b/lib/WikiDB/backend/PearDB_mysql.php
@@ -1,5 +1,5 @@
 sql();
+        if ($node->op == 'REGEX') { // posix regex extensions
+            return "pagename REGEXP '$word'";
+        } else {
+            return $this->_case_exact ? "pagename LIKE '$word'" 
+                                      : "LOWER(pagename) LIKE '$word'";
+        }
+    }
+}
+
 // (c-file-style: "gnu")
 // Local Variables:
 // mode: php
diff --git a/lib/WikiDB/backend/PearDB_oci8.php b/lib/WikiDB/backend/PearDB_oci8.php
index eaece3870..f0b9842b5 100644
--- a/lib/WikiDB/backend/PearDB_oci8.php
+++ b/lib/WikiDB/backend/PearDB_oci8.php
@@ -1,5 +1,5 @@
 op;
-        $page = $this->$method($node->word);
-        $exactword = $this->_quote($node->word);
-        return $this->_case_exact 
+        $page = $node->sql($word);
+        $exactword = $node->_sql_quote();
+        return $this->_case_exact
             ? "pagename LIKE '$page' OR DBMS_LOB.INSTR(content, '$exactword') > 0"
             : "LOWER(pagename) LIKE '$page' OR DBMS_LOB.INSTR(content, '$exactword') > 0";
     }
diff --git a/lib/WikiDB/backend/PearDB_pgsql.php b/lib/WikiDB/backend/PearDB_pgsql.php
index 50373f8a1..f4a138633 100644
--- a/lib/WikiDB/backend/PearDB_pgsql.php
+++ b/lib/WikiDB/backend/PearDB_pgsql.php
@@ -1,5 +1,5 @@
 op;
-        $word = $this->$method($node->word);
-        return $this->_case_exact 
-            ? "pagename LIKE '$word'"
-            : "pagename ILIKE '$word'";
+    function _pagename_match_clause($node) {
+        $word = $node->sql();
+        if ($node->op == 'REGEX') { // posix regex extensions
+            return $this->_case_exact ? "pagename ~* '$word'"
+                		      : "pagename ~ '$word'";
+        } else {
+            return $this->_case_exact ? "pagename LIKE '$word'" 
+                                      : "pagename ILIKE '$word'";
+        }
     }
     function _fulltext_match_clause($node) { 
-        $method = $node->op;
-        $word = $this->$method($node->word);
-        return $this->_case_exact 
-            ? "pagename LIKE '$word' OR content LIKE '$word'"
-            : "pagename ILIKE '$word' OR content ILIKE '$word'";
+        $word = $node->sql();
+        return $this->_pagename_match_clause($node) 
+            . $this->_case_exact
+              ? " OR content LIKE '$word'"
+              : " OR content ILIKE '$word'";
     }
 }
 
diff --git a/lib/WikiGroup.php b/lib/WikiGroup.php
index fff6a42e7..0a8e43c8d 100644
--- a/lib/WikiGroup.php
+++ b/lib/WikiGroup.php
@@ -1,5 +1,5 @@
 membership[$group];
         }
         $dbh = & $this->dbh;
-        $db_result = $dbh->query(sprintf($this->_is_member,$dbh->quote($this->username),
+        $db_result = $dbh->query(sprintf($this->_is_member,
+                                         $dbh->quote($this->username),
                                          $dbh->quote($group)));
         if ($db_result->numRows() > 0) {
             $this->membership[$group] = true;
@@ -674,7 +675,7 @@ class GroupDb_PearDB extends GroupDb {
     	$membership = array();
 
         $dbh = & $this->dbh;
-        $db_result = $dbh->query(sprintf($this->_user_groups,$dbh->quote($this->username)));
+        $db_result = $dbh->query(sprintf($this->_user_groups, $dbh->quote($this->username)));
         if ($db_result->numRows() > 0) {
             while (list($group) = $db_result->fetchRow(DB_FETCHMODE_ORDERED)) {
                 $membership[] = $group;
@@ -1092,6 +1093,9 @@ class GroupLdap extends WikiGroup {
 }
 
 // $Log: not supported by cvs2svn $
+// Revision 1.50  2004/11/24 18:58:41  rurban
+// bug #1063463
+//
 // Revision 1.49  2004/11/23 13:06:30  rurban
 // several fixes and suggestions by Charles Corrigan:
 // * fix GROUP_BOGO_USER check
diff --git a/lib/plugin/TitleSearch.php b/lib/plugin/TitleSearch.php
index 352a30cc0..1f94031b5 100644
--- a/lib/plugin/TitleSearch.php
+++ b/lib/plugin/TitleSearch.php
@@ -1,7 +1,7 @@
  is enough.
+ * Fancier Inputforms can be made using WikiForm Rich, to support regex and case_exact args.
+ *
+ * If only one pages is found and auto_redirect is true, this page is displayed immediatly, 
+ * otherwise the found pagelist is displayed.
+ * The workhorse TextSearchQuery converts the query string from google-style words 
+ * to the required DB backend expression.
+ *   (word and word) OR word, -word, "two words"
+ * regex=auto tries to detect simple glob-style wildcards and expressions, 
+ * like xx*, *xx, ^xx, xx$, ^word$.
  */
 class WikiPlugin_TitleSearch
 extends WikiPlugin
@@ -37,7 +49,7 @@ extends WikiPlugin
 
     function getVersion() {
         return preg_replace("/[Revision: $]/", '',
-                            "\$Revision: 1.25 $");
+                            "\$Revision: 1.26 $");
     }
 
     function getDefaultArguments() {
@@ -55,13 +67,12 @@ extends WikiPlugin
     }
     // info arg allows multiple columns
     // info=mtime,hits,summary,version,author,locked,minor
-    // exclude arg allows multiple pagenames exclude=HomePage,RecentChanges
+    // exclude arg allows multiple pagenames exclude=Php*,RecentChanges
 
     function run($dbi, $argstr, &$request, $basepage) {
         $args = $this->getArgs($argstr, $request);
         if (empty($args['s']))
             return '';
-        //extract($args);
 
         $query = new TextSearchQuery($args['s'], $args['case_exact'], $args['regex']);
         $pages = $dbi->titleSearch($query);
@@ -73,10 +84,12 @@ extends WikiPlugin
         }
         // Provide an unknown WikiWord link to allow for page creation
         // when a search returns no results
-        if (!$args['noheader'])
-            $pagelist->setCaption(fmt("Title search results for '%s'",
-                                      $pagelist->getTotal() == 0
-                                      ? WikiLink($args['s'], 'auto') : $args['s']));
+        if (!$args['noheader']) {
+            $s = $args['s'];
+            if (!$pagelist->getTotal() and !$query->_regex)
+                $s = WikiLink($args['s'], 'auto');
+            $pagelist->setCaption(fmt("Title search results for '%s'", $s));
+        }
 
         if ($args['auto_redirect'] && ($pagelist->getTotal() == 1)) {
             return HTML($request->redirect(WikiURL($last_name, false, 'absurl'), false),
@@ -88,6 +101,9 @@ extends WikiPlugin
 };
 
 // $Log: not supported by cvs2svn $
+// Revision 1.25  2004/11/26 18:39:02  rurban
+// new regex search parser and SQL backends (90% complete, glob and pcre backends missing)
+//
 // Revision 1.24  2004/11/25 08:30:58  rurban
 // dont extract args
 //
-- 
2.45.0