This repository has been archived by the owner on Apr 26, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #307 from matrix-org/erikj/search
Add basic search API
- Loading branch information
Showing
10 changed files
with
365 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
# -*- coding: utf-8 -*- | ||
# Copyright 2015 OpenMarket Ltd | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
from twisted.internet import defer | ||
|
||
from ._base import BaseHandler | ||
|
||
from synapse.api.constants import Membership | ||
from synapse.api.errors import SynapseError | ||
from synapse.events.utils import serialize_event | ||
|
||
import logging | ||
|
||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class SearchHandler(BaseHandler): | ||
|
||
def __init__(self, hs): | ||
super(SearchHandler, self).__init__(hs) | ||
|
||
@defer.inlineCallbacks | ||
def search(self, user, content): | ||
"""Performs a full text search for a user. | ||
Args: | ||
user (UserID) | ||
content (dict): Search parameters | ||
Returns: | ||
dict to be returned to the client with results of search | ||
""" | ||
|
||
try: | ||
search_term = content["search_categories"]["room_events"]["search_term"] | ||
keys = content["search_categories"]["room_events"].get("keys", [ | ||
"content.body", "content.name", "content.topic", | ||
]) | ||
except KeyError: | ||
raise SynapseError(400, "Invalid search query") | ||
|
||
# TODO: Search through left rooms too | ||
rooms = yield self.store.get_rooms_for_user_where_membership_is( | ||
user.to_string(), | ||
membership_list=[Membership.JOIN], | ||
# membership_list=[Membership.JOIN, Membership.LEAVE, Membership.Ban], | ||
) | ||
room_ids = set(r.room_id for r in rooms) | ||
|
||
# TODO: Apply room filter to rooms list | ||
|
||
rank_map, event_map = yield self.store.search_msgs(room_ids, search_term, keys) | ||
|
||
allowed_events = yield self._filter_events_for_client( | ||
user.to_string(), event_map.values() | ||
) | ||
|
||
# TODO: Filter allowed_events | ||
# TODO: Add a limit | ||
|
||
time_now = self.clock.time_msec() | ||
|
||
results = { | ||
e.event_id: { | ||
"rank": rank_map[e.event_id], | ||
"result": serialize_event(e, time_now) | ||
} | ||
for e in allowed_events | ||
} | ||
|
||
logger.info("Found %d results", len(results)) | ||
|
||
defer.returnValue({ | ||
"search_categories": { | ||
"room_events": { | ||
"results": results, | ||
"count": len(results) | ||
} | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
# Copyright 2015 OpenMarket Ltd | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
import logging | ||
|
||
from synapse.storage.prepare_database import get_statements | ||
from synapse.storage.engines import PostgresEngine, Sqlite3Engine | ||
|
||
import ujson | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
POSTGRES_SQL = """ | ||
CREATE TABLE event_search ( | ||
event_id TEXT, | ||
room_id TEXT, | ||
key TEXT, | ||
vector tsvector | ||
); | ||
INSERT INTO event_search SELECT | ||
event_id, room_id, 'content.body', | ||
to_tsvector('english', json::json->'content'->>'body') | ||
FROM events NATURAL JOIN event_json WHERE type = 'm.room.message'; | ||
INSERT INTO event_search SELECT | ||
event_id, room_id, 'content.name', | ||
to_tsvector('english', json::json->'content'->>'name') | ||
FROM events NATURAL JOIN event_json WHERE type = 'm.room.name'; | ||
INSERT INTO event_search SELECT | ||
event_id, room_id, 'content.topic', | ||
to_tsvector('english', json::json->'content'->>'topic') | ||
FROM events NATURAL JOIN event_json WHERE type = 'm.room.topic'; | ||
CREATE INDEX event_search_fts_idx ON event_search USING gin(vector); | ||
CREATE INDEX event_search_ev_idx ON event_search(event_id); | ||
CREATE INDEX event_search_ev_ridx ON event_search(room_id); | ||
""" | ||
|
||
|
||
SQLITE_TABLE = ( | ||
"CREATE VIRTUAL TABLE event_search USING fts3 ( event_id, room_id, key, value)" | ||
) | ||
|
||
|
||
def run_upgrade(cur, database_engine, *args, **kwargs): | ||
if isinstance(database_engine, PostgresEngine): | ||
run_postgres_upgrade(cur) | ||
return | ||
|
||
if isinstance(database_engine, Sqlite3Engine): | ||
run_sqlite_upgrade(cur) | ||
return | ||
|
||
|
||
def run_postgres_upgrade(cur): | ||
for statement in get_statements(POSTGRES_SQL.splitlines()): | ||
cur.execute(statement) | ||
|
||
|
||
def run_sqlite_upgrade(cur): | ||
cur.execute(SQLITE_TABLE) | ||
|
||
rowid = -1 | ||
while True: | ||
cur.execute( | ||
"SELECT rowid, json FROM event_json" | ||
" WHERE rowid > ?" | ||
" ORDER BY rowid ASC LIMIT 100", | ||
(rowid,) | ||
) | ||
|
||
res = cur.fetchall() | ||
|
||
if not res: | ||
break | ||
|
||
events = [ | ||
ujson.loads(js) | ||
for _, js in res | ||
] | ||
|
||
rowid = max(rid for rid, _ in res) | ||
|
||
rows = [] | ||
for ev in events: | ||
if ev["type"] == "m.room.message": | ||
rows.append(( | ||
ev["event_id"], ev["room_id"], "content.body", | ||
ev["content"]["body"] | ||
)) | ||
if ev["type"] == "m.room.name": | ||
rows.append(( | ||
ev["event_id"], ev["room_id"], "content.name", | ||
ev["content"]["name"] | ||
)) | ||
if ev["type"] == "m.room.topic": | ||
rows.append(( | ||
ev["event_id"], ev["room_id"], "content.topic", | ||
ev["content"]["topic"] | ||
)) | ||
|
||
if rows: | ||
logger.info(rows) | ||
cur.executemany( | ||
"INSERT INTO event_search (event_id, room_id, key, value)" | ||
" VALUES (?,?,?,?)", | ||
rows | ||
) |
Oops, something went wrong.