Merge lp:~ibid-dev/ibid/factoid-544493 into lp:~ibid-core/ibid/old-trunk-1.6

Proposed by Stefano Rivera
Status: Rejected
Rejected by: Stefano Rivera
Proposed branch: lp:~ibid-dev/ibid/factoid-544493
Merge into: lp:~ibid-core/ibid/old-trunk-1.6
Prerequisite: lp:~stefanor/ibid/regexp-595423
Diff against target: 248 lines (+89/-40) (has conflicts)
1 file modified
ibid/plugins/factoid.py (+89/-40)
Text conflict in ibid/plugins/factoid.py
To merge this branch: bzr merge lp:~ibid-dev/ibid/factoid-544493
Reviewer Review Type Date Requested Status
Ibid Dev Team Pending
Stefano Rivera Pending
Michael Gorven Pending
Review via email: mp+47292@code.launchpad.net

This proposal supersedes a proposal from 2010-03-27.

Description of the change

Fix deletion of factoids containing args.

To post a comment you must log in.
Revision history for this message
marcog (marco-gallotta) wrote : Posted in a previous version of this proposal

This appears to work. I hope I understand what it's doing. :)

Revision history for this message
Stefano Rivera (stefanor) wrote : Posted in a previous version of this proposal

Can you rebase this off release-0.1, or trunk r908, so it can be merged into both branches, please?

review: Needs Resubmitting
Revision history for this message
Stefano Rivera (stefanor) wrote : Posted in a previous version of this proposal

Then request merge into 0.1, trunk will come after 0.1 has the merge

Revision history for this message
Stefano Rivera (stefanor) wrote : Posted in a previous version of this proposal

OK, until we sort tarmac out, please rebase off trunk r908 and request merge into trunk

Revision history for this message
marcog (marco-gallotta) wrote : Posted in a previous version of this proposal

> OK, until we sort tarmac out, please rebase off trunk r908 and request merge
> into trunk

Done

Revision history for this message
Stefano Rivera (stefanor) wrote : Posted in a previous version of this proposal

Works for me

review: Approve
Revision history for this message
Michael Gorven (mgorven) wrote : Posted in a previous version of this proposal

 review approve

review: Approve
Revision history for this message
marcog (marco-gallotta) wrote : Posted in a previous version of this proposal

Right, I ended up finding and fixing quite a bit more than the original bug.

One bug I can't manage to fix (due to a lack of understanding of wtf the code is actually doing :P), is searching factoids containing $arg doesn't always work as I show here:

Query: a $arg is b
Response: One learns a new thing every day
Query: search b
Response: a $arg [1] <- works correctly
Query: search arg
Response: I couldn't find anything that matched 'arg' <- 'a $arg' should match 'arg', but doesn't

Then, could someone possibly explain what the two-pass search is for in get_factoid (see lines 221-225 and 230-234 in this branch)? I know what I changed will remove the second pass, but I want to understand it's intention so that I can get it working with my patch. I don't think I've ever seen wildcards used, so I'm not sure what wildcards are supported.

Revision history for this message
Stefano Rivera (stefanor) wrote : Posted in a previous version of this proposal

> .all()[number:]
> .all()[number]

Those alls are required for "I only know foo things", please add a comment explaining that.

> x.like(y, escape='\\')

You can't do that with MySQL. See the equivalent in get_factoid.

The new search code is too complicated, I think.
Does search really need to be implemented as two pass? Why not just skip $arg matching in search and translate $arg into _ instead?

review: Needs Fixing
Revision history for this message
Stefano Rivera (stefanor) wrote :

Actually don't review this quite yet, still needs some work...

Unmerged revisions

930. By Stefano Rivera

Merged regexp-595423

929. By Stefano Rivera

Use # as escape to get around all LIKE escaping issues

928. By Stefano Rivera

Use # as escape to get around all LIKE escaping issues

927. By marcog

Return number of values known for append when given index is too large

926. By marcog

all or literal -> all

925. By marcog

Make factoid search aware of args (for non-regex searches)

924. By marcog

All implies literal, so set it on top rather than check continuously

923. By marcog

Go through the 2nd pass of get_factoid() when no matches found in the 1st pass

922. By marcog

Report number of matches in factoid searching when outside the range

921. By marcog

Fix start/limit bounds for factoid searching

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ibid/plugins/factoid.py'
2--- ibid/plugins/factoid.py 2011-01-24 18:54:59 +0000
3+++ ibid/plugins/factoid.py 2011-01-24 18:55:00 +0000
4@@ -81,8 +81,12 @@
5 # http://www.sqlalchemy.org/trac/ticket/1400:
6 # We can't use .like() in MySQL
7 for row in self.upgrade_session.query(FactoidName) \
8+<<<<<<< TREE
9 .filter('lower(name) LIKE lower(:pattern) ESCAPE :escape') \
10 .params(pattern='%\\_\\%%', escape='\\') \
11+=======
12+ .filter(FactoidName.name.like('%#_#%%', escape='#')) \
13+>>>>>>> MERGE-SOURCE
14 .all():
15 row.wild = True
16 self.upgrade_session.save_or_update(row)
17@@ -210,18 +214,23 @@
18
19 action_re = re.compile(r'^\s*<action>\s*')
20 reply_re = re.compile(r'^\s*<reply>\s*')
21-escape_like_re = re.compile(r'([%_\\])')
22+escape_like_re = re.compile(r'([%_#])')
23
24 def get_factoid(session, name, number, pattern, is_regex, all=False,
25 literal=False):
26 """session: SQLAlchemy session
27 name: Factoid name (can contain arguments unless literal query)
28- number: Return factoid[number] (or factoid[number:] for literal queries)
29- pattern: Return factoids matching this pattern (cannot be used in conjuction with number)
30+ number: If not None, restrict to factoid[number] (or factoid[number:] for literal queries)
31+ pattern: If not None, restrict to factoids matching this pattern (cannot be used in conjuction with number)
32 is_regex: Pattern is a real regex
33 all: Return a random factoid from the set if False
34 literal: Match factoid name literally (implies all)
35+ if all or literal, a list is returned; otherwise a single factoid is returned
36+ if nothing is found, either an empty list or None is returned
37 """
38+ assert not (number and pattern), u'number and pattern cannot be used together'
39+ if literal:
40+ all = True # as mentioned in the docstring
41 # First pass for exact matches, if necessary again for wildcard matches
42 if literal:
43 passes = (False,)
44@@ -235,9 +244,14 @@
45 if wild:
46 # Reversed LIKE because factoid name contains SQL wildcards if
47 # factoid supports arguments
48+<<<<<<< TREE
49 query = query.filter(
50 'lower(:fact) LIKE lower(name) ESCAPE :escape'
51 ).params(fact=name, escape='\\')
52+=======
53+ query = query.filter(':fact LIKE name ESCAPE :escape') \
54+ .params(fact=name, escape='#')
55+>>>>>>> MERGE-SOURCE
56 else:
57 query = query.filter(FactoidName.name == escape_name(name))
58 # For normal matches, restrict to the subset applicable
59@@ -249,22 +263,29 @@
60 op = get_regexp_op(session)
61 query = query.filter(op(FactoidValue.value, pattern))
62 else:
63+<<<<<<< TREE
64 pattern = '%%%s%%' % escape_like_re.sub(r'\\\1', pattern)
65 # http://www.sqlalchemy.org/trac/ticket/1400:
66 # We can't use .like() in MySQL
67 query = query.filter(
68 'lower(value) LIKE lower(:pattern) ESCAPE :escape'
69 ).params(pattern=pattern, escape='\\')
70+=======
71+ pattern = '%%%s%%' % escape_like_re.sub(r'#\1', pattern)
72+ query = query.filter(FactoidValue.value.like(pattern,
73+ escape='#'))
74+>>>>>>> MERGE-SOURCE
75
76- if number:
77+ if number is not None:
78+ number = max(0, int(number) - 1)
79 try:
80 if literal:
81- return query.order_by(FactoidValue.id)[int(number) - 1:]
82+ return query.order_by(FactoidValue.id).all()[number:]
83 else:
84- factoid = query.order_by(FactoidValue.id)[int(number) - 1]
85+ factoid = query.order_by(FactoidValue.id).all()[number]
86 except IndexError:
87 continue
88- if all or literal:
89+ if all:
90 if factoid is not None:
91 return [factoid]
92 else:
93@@ -273,8 +294,9 @@
94 factoid = factoid or query.order_by(func.random()).first()
95 if factoid:
96 return factoid
97- if all:
98- return []
99+ if all:
100+ return []
101+ return None
102
103 class Utils(Processor):
104 usage = u'literal <name> [( #<from number> | /<pattern>/[r] )]'
105@@ -289,6 +311,15 @@
106 event.addresponse(u', '.join(u'%i: %s'
107 % (index + number, value.value)
108 for index, (factoid, name, value) in enumerate(factoids)))
109+ else:
110+ factoids = get_factoid(event.session, name, None, pattern, is_regex,
111+ literal=True)
112+ if factoids:
113+ event.addresponse(u"I only know %(number)d things about %(name)s", {
114+ u'number': len(factoids),
115+ u'name': name,
116+ })
117+
118
119 class Forget(Processor):
120 usage = u"""forget <name> [( #<number> | /<pattern>/[r] )]
121@@ -302,17 +333,17 @@
122 @match(r'^forget\s+(.+?)(?:\s+#(\d+)|\s+(?:/(.+?)/(r?)))?$')
123 @authorise(fallthrough=False)
124 def forget(self, event, name, number, pattern, is_regex):
125- factoids = get_factoid(event.session, name, number, pattern, is_regex, all=True)
126+ factoids = get_factoid(event.session, name, number, pattern, is_regex, all=True, literal=True)
127 if factoids:
128 factoidadmin = auth_responses(event, u'factoidadmin')
129 identities = get_identities(event)
130 factoid = event.session.query(Factoid).get(factoids[0][0].id)
131
132- if (number or pattern):
133- if len(factoids) > 1:
134- event.addresponse(u'Pattern matches multiple factoids, please be more specific')
135- return
136+ if len(factoids) > 1 and pattern is not None:
137+ event.addresponse(u'Pattern matches multiple factoids, please be more specific')
138+ return
139
140+ if number or pattern:
141 if factoids[0][2].identity_id not in identities and not factoidadmin:
142 return
143
144@@ -355,7 +386,14 @@
145
146 event.addresponse(True)
147 else:
148- event.addresponse(u"I didn't know about %s anyway", name)
149+ factoids = get_factoid(event.session, name, None, pattern, is_regex, all=True, literal=True)
150+ if factoids:
151+ event.addresponse(u"I only know %(number)d things about %(name)s", {
152+ u'number': len(factoids),
153+ u'name': name,
154+ })
155+ else:
156+ event.addresponse(u"I didn't know about %s anyway", name)
157
158 @match(r'^(.+)\s+is\s+the\s+same\s+as\s+(.+)$')
159 @authorise(fallthrough=False)
160@@ -415,35 +453,39 @@
161 pattern = m.group(1)
162 is_regex = bool(m.group(2))
163
164+ if is_regex:
165+ filter_op = get_regexp_op(event.session)
166+ name_pattern = pattern.replace(r'\$arg', '_%')
167+ else:
168+ filter_op = lambda x, y: x.like(y, escape='#')
169+ pattern = '%%%s%%' % escape_like_re.sub(r'#\1', pattern)
170+ name_pattern = pattern.replace('$arg', '#_#%')
171+
172 query = event.session.query(Factoid)\
173- .join(Factoid.names).add_entity(FactoidName)\
174- .join(Factoid.values)
175+ .join(Factoid.names).add_entity(FactoidName)\
176+ .join(Factoid.values)
177
178 if search_type.startswith('fact'):
179- filter_on = (FactoidName.name,)
180+ query = query.filter(filter_op(FactoidName.name, name_pattern))
181 elif search_type.startswith('value'):
182- filter_on = (FactoidValue.value,)
183- else:
184- filter_on = (FactoidName.name, FactoidValue.value)
185-
186- if is_regex:
187- filter_op = get_regexp_op(event.session)
188- else:
189- pattern = "%%%s%%" % escape_like_re.sub(r'\\\1', pattern)
190- filter_op = lambda x, y: x.like(y)
191-
192- if len(filter_on) == 1:
193- query = query.filter(filter_op(filter_on[0], pattern))
194- else:
195- query = query.filter(or_(filter_op(filter_on[0], pattern), filter_op(filter_on[1], pattern)))
196-
197- # Pre-evalute the iterable or the if statement will be True in SQLAlchemy 0.4 [Bug #383286]
198- matches = [match for match in query[start:start+limit]]
199-
200- if matches:
201+ query = query.filter(filter_op(FactoidValue.value, pattern))
202+ else:
203+ query = query.filter(or_(filter_op(FactoidName.name, name_pattern),
204+ filter_op(FactoidValue.value, pattern)))
205+
206+ # Pre-evalute the iterable or the if statement will be True in SQLAlchemy 0.4. LP: #383286
207+ matches = [match for match in query.all()]
208+
209+ bounded_matches = matches[start:start+limit]
210+ if bounded_matches:
211 event.addresponse(u'; '.join(
212 u'%s [%s]' % (fname.name, len(factoid.values))
213 for factoid, fname in matches))
214+ elif len(matches):
215+ event.addresponse(u"I could only find %(number)d things that matched '%(pattern)s'", {
216+ u'number': len(matches),
217+ u'pattern': origpattern,
218+ })
219 else:
220 event.addresponse(u"I couldn't find anything that matched '%s'" % origpattern)
221
222@@ -623,7 +665,7 @@
223 @authorise(fallthrough=False)
224 def append(self, event, name, number, pattern, is_regex, suffix):
225 name = strip_name(name)
226- factoids = get_factoid(event.session, name, number, pattern, is_regex, all=True)
227+ factoids = get_factoid(event.session, name, number, pattern, is_regex, all=True, literal=True)
228 if len(factoids) == 0:
229 if pattern:
230 event.addresponse(u"I don't know about any %(name)s matching %(pattern)s", {
231@@ -631,8 +673,15 @@
232 'pattern': pattern,
233 })
234 else:
235- event.addresponse(u"I don't know about %s", name)
236- elif len(factoids) > 1:
237+ factoids = get_factoid(event.session, name, None, pattern, is_regex, all=True)
238+ if factoids:
239+ event.addresponse(u"I only know %(number)d things about %(name)s", {
240+ u'number': len(factoids),
241+ u'name': name,
242+ })
243+ else:
244+ event.addresponse(u"I don't know about %s", name)
245+ elif len(factoids) > 1 and number is None:
246 event.addresponse(u"Pattern matches multiple factoids, please be more specific")
247 else:
248 factoidadmin = auth_responses(event, u'factoidadmin')

Subscribers

People subscribed via source and target branches