Modify

Opened 7 years ago

Closed 7 years ago

Last modified 7 years ago

#12915 closed defect (fixed)

Require Trac v1.0.2 installation onwards

Reported by: ntmlod Owned by: Jun Omae
Priority: normal Component: WikiAutoCompletePlugin
Severity: normal Keywords:
Cc: Trac Release: 1.0

Description

When attempting to write links to ticket or wiki page, I get no suggestions and the following error in the logging:

Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/trac/web/main.py", line 497, in _dispatch_request
    dispatcher.dispatch(req)
  File "/usr/lib/python2.7/site-packages/trac/web/main.py", line 214, in dispatch
    resp = chosen_handler.process_request(req)
  File "build/bdist.linux-x86_64/egg/wikiautocomplete/web_ui.py", line 87, in process_request
    """ % db.prefix_match(),
  File "/usr/lib/python2.7/site-packages/trac/db/util.py", line 108, in __getattr__
    return getattr(self.cnx, name)
  File "/usr/lib/python2.7/site-packages/trac/db/util.py", line 108, in __getattr__
    return getattr(self.cnx, name)
  File "/usr/lib/python2.7/site-packages/trac/db/util.py", line 108, in __getattr__
    return getattr(self.cnx, name)
AttributeError: 'psycopg2._psycopg.connection' object has no attribute 'prefix_match'

To generate completions for 'ticket' or 'wikipage' case, the plugin use db.prefix_match & db.prefix_match_value methods which are missing in db directory of our v1.0.1 installation. Actually, these methods have been implemented since 1.0.2 version: https://trac.edgewall.org/changeset/13016.

Probably nothing essential for the plugin, I'm going to try to get this works.

Attachments (0)

Change History (11)

comment:1 Changed 7 years ago by Jun Omae

In that case, db.prefix_match() is used with CAST(id AS TEXT). It would leads full scan for ticket table and slowness if many tickets.

After the following patch, it would scan tickets with primary key of the ticket table.

  • wikiautocompleteplugin/trunk/wikiautocomplete/web_ui.py

    diff --git a/wikiautocompleteplugin/trunk/wikiautocomplete/web_ui.py b/wikiautocompleteplugin/trunk/wikiautocomplete/web_ui.py
    index d57b338..948e093 100644
    a b from json import JSONEncoder 
    66
    77from trac.core import *
    88from trac.resource import Resource
     9from trac.ticket.model import Ticket
    910from trac.util.text import to_unicode
    1011from trac.util.translation import dgettext
    1112from trac.web import IRequestFilter, IRequestHandler
    class WikiAutoCompleteModule(Component): 
    6061            self._send_json(req, completions)
    6162
    6263        elif strategy == 'ticket':
    63             with self.env.db_query as db:
    64                 rows = db("""
    65                     SELECT id, summary
    66                     FROM ticket
    67                     WHERE %s %s
     64            try:
     65                num = int(term)
     66            except:
     67                num = 0
     68            ids = []
     69            while num > 0 and Ticket.id_is_valid(num):
     70                ids.append(num)
     71                num *= 10
     72            if ids:
     73                rows = self.env.db_query("""
     74                    SELECT id, summary FROM ticket
     75                    WHERE id IN (%s)
    6876                    ORDER BY changetime DESC
    6977                    LIMIT 10
    70                     """ % (db.cast('id', 'text'), db.prefix_match()),
    71                     (db.prefix_match_value(term), ))
     78                    """ % ','.join(['%s'] * len(ids)),
     79                    ids)
     80            else:
     81                rows = []
    7282            completions = [{
    7383                'id': row[0],
    7484                'summary': row[1],

comment:2 Changed 7 years ago by Jun Omae

Also, typing ticket: leads no suggestion. It would be good to suggest when typing ticket: as the same as #.

  • wikiautocompleteplugin/trunk/wikiautocomplete/htdocs/js/wikiautocomplete.js

    diff --git a/wikiautocompleteplugin/trunk/wikiautocomplete/htdocs/js/wikiautocomplete.js b/wikiautocompleteplugin/trunk/wikiautocomp
    index 22e1899..0699a38 100644
    a b jQuery(document).ready(function($) { 
    1515        },
    1616
    1717        { // Tickets
    18             match: /#(\d*)$/,
     18            match: /(?:#|\bticket:)(\d*)$/,
    1919            search: function (term, callback) {
    2020                $.getJSON(wikiautocomplete_url + '/ticket', { q: term })
    2121                    .done(function (resp) { callback(resp); })
Last edited 7 years ago by Jun Omae (previous) (diff)

comment:3 in reply to:  1 ; Changed 7 years ago by Peter Suter

Replying to jun66j5:

After the following patch, it would scan tickets with primary key of the ticket table.

For 77 would that still find 771, 772, 773, ... or only 770, 7700, 77000, ...?

comment:4 Changed 7 years ago by ntmlod

Well for once I managed to fix it for my environment but perhaps it is not robust enough.

  • wikiautocomplete/web_ui.py

     
    6464                rows = db("""
    6565                    SELECT id, summary
    6666                    FROM ticket
    67                     WHERE %s %s
     67                    WHERE %s LIKE '%s%%'
    6868                    ORDER BY changetime DESC
    6969                    LIMIT 10
    70                     """ % (db.cast('id', 'text'), db.prefix_match()),
    71                     (db.prefix_match_value(term), ))
     70                    """ % (db.cast('id', 'text'), term))
    7271            completions = [{
    7372                'id': row[0],
    7473                'summary': row[1],
     
    8180                rows = db("""
    8281                    SELECT name
    8382                    FROM wiki
    84                     WHERE name %s
     83                    WHERE name LIKE '%s%%'
    8584                    GROUP BY name
    8685                    ORDER BY name
    8786                    LIMIT 10
    88                     """ % db.prefix_match(),
    89                     (db.prefix_match_value(term), ))
     87                    """ % term)
    9088            completions = [row[0] for row in rows
    9189                           if 'WIKI_VIEW' in req.perm(Resource('wiki', row[0]))]
    9290            self._send_json(req, completions)

comment:5 Changed 7 years ago by ntmlod

Also I modified the js script to split the two cases for 'ticket' links (short one with # and long one with ticket: or [ticket:... ...] because the previous proposition by jun66j5 replace [ticket:... ...] by [#... ...] which is not working.

  • wikiautocomplete/htdocs/js/wikiautocomplete.js

     
    3131            cache: true,
    3232        },
    3333
     34        { // Tickets
     35            match: /\bticket:(\w*)$/,
     36            search: function (term, callback) {
     37                $.getJSON(wikiautocomplete_url + '/ticket', { q: term })
     38                    .done(function (resp) { callback(resp); })
     39                    .fail(function () { callback([]); });
     40            },
     41            index: 1,
     42            template: function (ticket) {
     43                return '#' + ticket.id + ' ' + ticket.summary;
     44            },
     45            replace: function (ticket) {
     46                return 'ticket:' + ticket.id;
     47            },
     48            cache: true,
     49        },
     50
    3451        { // Wiki pages
    3552            match: /\bwiki:([\w/]*)$/,
    3653            search: function (term, callback) {

Probably not optimized because I guess it is possible to switch the cases after the string match but I'm a real newbie in Js.

I think there is room for improvment in order to add some others Trac useful links like changesets & reports for examples. Perhaps in my easy going time.

comment:6 in reply to:  3 Changed 7 years ago by Jun Omae

Replying to psuter:

Replying to jun66j5:

After the following patch, it would scan tickets with primary key of the ticket table.

For 77 would that still find 771, 772, 773, ... or only 770, 7700, 77000, ...?

Sorry. I was stupid.

Revised patch:

  • wikiautocompleteplugin/trunk/wikiautocomplete/web_ui.py

    diff --git a/wikiautocompleteplugin/trunk/wikiautocomplete/web_ui.py b/wikiautocompleteplugin/trunk/wikiautocomplete/web_ui.py
    index d57b338..0f5536b 100644
    a b from json import JSONEncoder 
    66
    77from trac.core import *
    88from trac.resource import Resource
     9from trac.ticket.model import Ticket
    910from trac.util.text import to_unicode
    1011from trac.util.translation import dgettext
    1112from trac.web import IRequestFilter, IRequestHandler
    class WikiAutoCompleteModule(Component): 
    6061            self._send_json(req, completions)
    6162
    6263        elif strategy == 'ticket':
    63             with self.env.db_query as db:
    64                 rows = db("""
    65                     SELECT id, summary
    66                     FROM ticket
    67                     WHERE %s %s
     64            try:
     65                num = int(term)
     66            except:
     67                num = 0
     68            args = []
     69            mul = 1
     70            while num > 0 and Ticket.id_is_valid(num):
     71                args.append(num)
     72                args.append(num + mul)
     73                num *= 10
     74                mul *= 10
     75            if args:
     76                expr = ' OR '.join(['id>=%s AND id<%s'] * (len(args) / 2))
     77                rows = self.env.db_query("""
     78                    SELECT id, summary FROM ticket
     79                    WHERE %(expr)s
    6880                    ORDER BY changetime DESC
    6981                    LIMIT 10
    70                     """ % (db.cast('id', 'text'), db.prefix_match()),
    71                     (db.prefix_match_value(term), ))
     82                    """ % {'expr': expr}, args)
     83            else:
     84                rows = self.env.db_query("""
     85                    SELECT id, summary FROM ticket
     86                    ORDER BY changetime DESC LIMIT 10""")
    7287            completions = [{
    7388                'id': row[0],
    7489                'summary': row[1],

Typing 77, the following query is generated.

sqlite> EXPLAIN QUERY PLAN
   ...> SELECT id, summary FROM ticket
   ...> WHERE id>=77 AND id<78 OR id>=770 AND id<780 OR id>=7700 AND id<7800 OR id>=77000 AND id<78000 OR id>=770000 AND id<780000 OR id>=7700000 AND id<7800000 OR id>=77000000 AND id<78000000 OR id>=770000000 AND id<780000000
   ...> ORDER BY changetime DESC
   ...> LIMIT 10;
0|0|0|SEARCH TABLE ticket USING INTEGER PRIMARY KEY (rowid>? AND rowid<?)
0|0|0|SEARCH TABLE ticket USING INTEGER PRIMARY KEY (rowid>? AND rowid<?)
0|0|0|SEARCH TABLE ticket USING INTEGER PRIMARY KEY (rowid>? AND rowid<?)
0|0|0|SEARCH TABLE ticket USING INTEGER PRIMARY KEY (rowid>? AND rowid<?)
0|0|0|SEARCH TABLE ticket USING INTEGER PRIMARY KEY (rowid>? AND rowid<?)
0|0|0|SEARCH TABLE ticket USING INTEGER PRIMARY KEY (rowid>? AND rowid<?)
0|0|0|SEARCH TABLE ticket USING INTEGER PRIMARY KEY (rowid>? AND rowid<?)
0|0|0|SEARCH TABLE ticket USING INTEGER PRIMARY KEY (rowid>? AND rowid<?)
0|0|0|USE TEMP B-TREE FOR ORDER BY
Last edited 7 years ago by Jun Omae (previous) (diff)

comment:7 in reply to:  4 Changed 7 years ago by Jun Omae

Replying to ntmlod:

Well for once I managed to fix it for my environment but perhaps it is not robust enough.

-                    WHERE name %s
+                    WHERE name LIKE '%s%%'
                     GROUP BY name
                     ORDER BY name
                     LIMIT 10
-                    """ % db.prefix_match(),
-                    (db.prefix_match_value(term), ))
+                    """ % term)

The patch has SQL injections....

We could retrieve all page names using WikiSystem().pages.

  • wikiautocompleteplugin/trunk/wikiautocomplete/web_ui.py

    diff --git a/wikiautocompleteplugin/trunk/wikiautocomplete/web_ui.py b/wikiautocompleteplugin/trunk/wikiautocomplete/web_ui.py
    index d57b338..ea01517 100644
    a b class WikiAutoCompleteModule(Component): 
    7790            self._send_json(req, completions)
    7891
    7992        elif strategy == 'wikipage':
    80             with self.env.db_query as db:
    81                 rows = db("""
    82                     SELECT name
    83                     FROM wiki
    84                     WHERE name %s
    85                     GROUP BY name
    86                     ORDER BY name
    87                     LIMIT 10
    88                     """ % db.prefix_match(),
    89                     (db.prefix_match_value(term), ))
    90             completions = [row[0] for row in rows
    91                            if 'WIKI_VIEW' in req.perm(Resource('wiki', row[0]))]
     93            pages = sorted(page for page in WikiSystem(self.env).pages
     94                                if page.startswith(term) and
     95                                   'WIKI_VIEW' in req.perm('wiki', page))
     96            completions = pages[:10]
    9297            self._send_json(req, completions)
    9398
    9499        elif strategy == 'macro':

comment:9 in reply to:  8 Changed 7 years ago by Peter Suter

Replying to jun66j5:

Proposed changes in https://github.com/jun66j5/wikiautocompleteplugin/compare/master...t12915.

Looks good to me. Feel free to commit.

comment:10 Changed 7 years ago by Jun Omae

Resolution: fixed
Status: newclosed

In 15939:

WikiAutoCompletePlugin: remove uses of db.prefix_match() (closes #12915)

  • use WikiSystem.pages rather than executing query with db.prefix_match()
  • use range scan for id column of ticket table rather than db.prefix_match() with CAST (id AS TEXT)
  • sort suggestion for macro and send first 10 entries only
  • make compatible with Python 2.5

comment:11 Changed 7 years ago by Jun Omae

Owner: changed from Peter Suter to Jun Omae

Thanks for the reviewing! Committed in [15939].

Modify Ticket

Change Properties
Set your email in Preferences
Action
as closed The owner will remain Jun Omae.
The resolution will be deleted. Next status will be 'reopened'.

Add Comment


E-mail address and name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.