Add Query API for querying events by ID (#129)

This commit is contained in:
Mark Haines 2017-06-02 14:32:36 +01:00 committed by GitHub
parent 1b6e06aa59
commit ef7b934d51
4 changed files with 118 additions and 2 deletions

View file

@ -41,6 +41,7 @@ type QueryLatestEventsAndStateResponse struct {
// The latest events in the room. // The latest events in the room.
LatestEvents []gomatrixserverlib.EventReference LatestEvents []gomatrixserverlib.EventReference
// The state events requested. // The state events requested.
// This list will be in an arbitrary order.
StateEvents []gomatrixserverlib.Event StateEvents []gomatrixserverlib.Event
} }
@ -65,9 +66,30 @@ type QueryStateAfterEventsResponse struct {
// If some of previous events do not exist this will be false and StateEvents will be empty. // If some of previous events do not exist this will be false and StateEvents will be empty.
PrevEventsExist bool PrevEventsExist bool
// The state events requested. // The state events requested.
// This list will be in an arbitrary order.
StateEvents []gomatrixserverlib.Event StateEvents []gomatrixserverlib.Event
} }
// QueryEventsByIDRequest is a request to QueryEventsByID
type QueryEventsByIDRequest struct {
// The event IDs to look up.
EventIDs []string
}
// QueryEventsByIDResponse is a response to QueryEventsByID
type QueryEventsByIDResponse struct {
// Copy of the request for debugging.
QueryEventsByIDRequest
// A list of events with the requested IDs.
// If the roomserver does not have a copy of a requested event
// then it will omit that event from the list.
// If the roomserver thinks it has a copy of the event, but
// fails to read it from the database then it will fail
// the entire request.
// This list will be in an arbitrary order.
Events []gomatrixserverlib.Event
}
// RoomserverQueryAPI is used to query information from the room server. // RoomserverQueryAPI is used to query information from the room server.
type RoomserverQueryAPI interface { type RoomserverQueryAPI interface {
// Query the latest events and state for a room from the room server. // Query the latest events and state for a room from the room server.
@ -81,6 +103,12 @@ type RoomserverQueryAPI interface {
request *QueryStateAfterEventsRequest, request *QueryStateAfterEventsRequest,
response *QueryStateAfterEventsResponse, response *QueryStateAfterEventsResponse,
) error ) error
// Query a list of events by event ID.
QueryEventsByID(
request *QueryEventsByIDRequest,
response *QueryEventsByIDResponse,
) error
} }
// RoomserverQueryLatestEventsAndStatePath is the HTTP path for the QueryLatestEventsAndState API. // RoomserverQueryLatestEventsAndStatePath is the HTTP path for the QueryLatestEventsAndState API.
@ -89,6 +117,9 @@ const RoomserverQueryLatestEventsAndStatePath = "/api/roomserver/QueryLatestEven
// RoomserverQueryStateAfterEventsPath is the HTTP path for the QueryStateAfterEvents API. // RoomserverQueryStateAfterEventsPath is the HTTP path for the QueryStateAfterEvents API.
const RoomserverQueryStateAfterEventsPath = "/api/roomserver/QueryStateAfterEvents" const RoomserverQueryStateAfterEventsPath = "/api/roomserver/QueryStateAfterEvents"
// RoomserverQueryEventsByIDPath is the HTTP path for the QueryEventsByID API.
const RoomserverQueryEventsByIDPath = "/api/roomserver/QueryEventsByID"
// NewRoomserverQueryAPIHTTP creates a RoomserverQueryAPI implemented by talking to a HTTP POST API. // NewRoomserverQueryAPIHTTP creates a RoomserverQueryAPI implemented by talking to a HTTP POST API.
// If httpClient is nil then it uses the http.DefaultClient // If httpClient is nil then it uses the http.DefaultClient
func NewRoomserverQueryAPIHTTP(roomserverURL string, httpClient *http.Client) RoomserverQueryAPI { func NewRoomserverQueryAPIHTTP(roomserverURL string, httpClient *http.Client) RoomserverQueryAPI {
@ -121,6 +152,15 @@ func (h *httpRoomserverQueryAPI) QueryStateAfterEvents(
return postJSON(h.httpClient, apiURL, request, response) return postJSON(h.httpClient, apiURL, request, response)
} }
// QueryEventsByID implements RoomserverQueryAPI
func (h *httpRoomserverQueryAPI) QueryEventsByID(
request *QueryEventsByIDRequest,
response *QueryEventsByIDResponse,
) error {
apiURL := h.roomserverURL + RoomserverQueryEventsByIDPath
return postJSON(h.httpClient, apiURL, request, response)
}
func postJSON(httpClient http.Client, apiURL string, request, response interface{}) error { func postJSON(httpClient http.Client, apiURL string, request, response interface{}) error {
jsonBytes, err := json.Marshal(request) jsonBytes, err := json.Marshal(request)
if err != nil { if err != nil {

View file

@ -35,6 +35,9 @@ type RoomserverQueryAPIDatabase interface {
// Lookup event references for the latest events in the room and the current state snapshot. // Lookup event references for the latest events in the room and the current state snapshot.
// Returns an error if there was a problem talking to the database. // Returns an error if there was a problem talking to the database.
LatestEventIDs(roomNID types.RoomNID) ([]gomatrixserverlib.EventReference, types.StateSnapshotNID, error) LatestEventIDs(roomNID types.RoomNID) ([]gomatrixserverlib.EventReference, types.StateSnapshotNID, error)
// Lookup the numeric IDs for a list of events.
// Returns an error if there was a problem talking to the database.
EventNIDs(eventIDs []string) (map[string]types.EventNID, error)
} }
// RoomserverQueryAPI is an implementation of RoomserverQueryAPI // RoomserverQueryAPI is an implementation of RoomserverQueryAPI
@ -46,7 +49,7 @@ type RoomserverQueryAPI struct {
func (r *RoomserverQueryAPI) QueryLatestEventsAndState( func (r *RoomserverQueryAPI) QueryLatestEventsAndState(
request *api.QueryLatestEventsAndStateRequest, request *api.QueryLatestEventsAndStateRequest,
response *api.QueryLatestEventsAndStateResponse, response *api.QueryLatestEventsAndStateResponse,
) (err error) { ) error {
response.QueryLatestEventsAndStateRequest = *request response.QueryLatestEventsAndStateRequest = *request
roomNID, err := r.DB.RoomNID(request.RoomID) roomNID, err := r.DB.RoomNID(request.RoomID)
if err != nil { if err != nil {
@ -81,7 +84,7 @@ func (r *RoomserverQueryAPI) QueryLatestEventsAndState(
func (r *RoomserverQueryAPI) QueryStateAfterEvents( func (r *RoomserverQueryAPI) QueryStateAfterEvents(
request *api.QueryStateAfterEventsRequest, request *api.QueryStateAfterEventsRequest,
response *api.QueryStateAfterEventsResponse, response *api.QueryStateAfterEventsResponse,
) (err error) { ) error {
response.QueryStateAfterEventsRequest = *request response.QueryStateAfterEventsRequest = *request
roomNID, err := r.DB.RoomNID(request.RoomID) roomNID, err := r.DB.RoomNID(request.RoomID)
if err != nil { if err != nil {
@ -115,12 +118,41 @@ func (r *RoomserverQueryAPI) QueryStateAfterEvents(
return nil return nil
} }
// QueryEventsByID implements api.RoomserverQueryAPI
func (r *RoomserverQueryAPI) QueryEventsByID(
request *api.QueryEventsByIDRequest,
response *api.QueryEventsByIDResponse,
) error {
response.QueryEventsByIDRequest = *request
eventNIDMap, err := r.DB.EventNIDs(request.EventIDs)
if err != nil {
return err
}
var eventNIDs []types.EventNID
for _, nid := range eventNIDMap {
eventNIDs = append(eventNIDs, nid)
}
events, err := r.loadEvents(eventNIDs)
if err != nil {
return err
}
response.Events = events
return nil
}
func (r *RoomserverQueryAPI) loadStateEvents(stateEntries []types.StateEntry) ([]gomatrixserverlib.Event, error) { func (r *RoomserverQueryAPI) loadStateEvents(stateEntries []types.StateEntry) ([]gomatrixserverlib.Event, error) {
eventNIDs := make([]types.EventNID, len(stateEntries)) eventNIDs := make([]types.EventNID, len(stateEntries))
for i := range stateEntries { for i := range stateEntries {
eventNIDs[i] = stateEntries[i].EventNID eventNIDs[i] = stateEntries[i].EventNID
} }
return r.loadEvents(eventNIDs)
}
func (r *RoomserverQueryAPI) loadEvents(eventNIDs []types.EventNID) ([]gomatrixserverlib.Event, error) {
stateEvents, err := r.DB.Events(eventNIDs) stateEvents, err := r.DB.Events(eventNIDs)
if err != nil { if err != nil {
return nil, err return nil, err
@ -163,4 +195,18 @@ func (r *RoomserverQueryAPI) SetupHTTP(servMux *http.ServeMux) {
return util.JSONResponse{Code: 200, JSON: &response} return util.JSONResponse{Code: 200, JSON: &response}
}), }),
) )
servMux.Handle(
api.RoomserverQueryEventsByIDPath,
common.MakeAPI("query_events_by_id", func(req *http.Request) util.JSONResponse {
var request api.QueryEventsByIDRequest
var response api.QueryEventsByIDResponse
if err := json.NewDecoder(req.Body).Decode(&request); err != nil {
return util.ErrorResponse(err)
}
if err := r.QueryEventsByID(&request, &response); err != nil {
return util.ErrorResponse(err)
}
return util.JSONResponse{Code: 200, JSON: &response}
}),
)
} }

View file

@ -104,6 +104,9 @@ const bulkSelectEventReferenceSQL = "" +
const bulkSelectEventIDSQL = "" + const bulkSelectEventIDSQL = "" +
"SELECT event_nid, event_id FROM events WHERE event_nid = ANY($1)" "SELECT event_nid, event_id FROM events WHERE event_nid = ANY($1)"
const bulkSelectEventNIDSQL = "" +
"SELECT event_id, event_nid FROM events WHERE event_id = ANY($1)"
type eventStatements struct { type eventStatements struct {
insertEventStmt *sql.Stmt insertEventStmt *sql.Stmt
selectEventStmt *sql.Stmt selectEventStmt *sql.Stmt
@ -116,6 +119,7 @@ type eventStatements struct {
bulkSelectStateAtEventAndReferenceStmt *sql.Stmt bulkSelectStateAtEventAndReferenceStmt *sql.Stmt
bulkSelectEventReferenceStmt *sql.Stmt bulkSelectEventReferenceStmt *sql.Stmt
bulkSelectEventIDStmt *sql.Stmt bulkSelectEventIDStmt *sql.Stmt
bulkSelectEventNIDStmt *sql.Stmt
} }
func (s *eventStatements) prepare(db *sql.DB) (err error) { func (s *eventStatements) prepare(db *sql.DB) (err error) {
@ -136,6 +140,7 @@ func (s *eventStatements) prepare(db *sql.DB) (err error) {
{&s.bulkSelectStateAtEventAndReferenceStmt, bulkSelectStateAtEventAndReferenceSQL}, {&s.bulkSelectStateAtEventAndReferenceStmt, bulkSelectStateAtEventAndReferenceSQL},
{&s.bulkSelectEventReferenceStmt, bulkSelectEventReferenceSQL}, {&s.bulkSelectEventReferenceStmt, bulkSelectEventReferenceSQL},
{&s.bulkSelectEventIDStmt, bulkSelectEventIDSQL}, {&s.bulkSelectEventIDStmt, bulkSelectEventIDSQL},
{&s.bulkSelectEventNIDStmt, bulkSelectEventNIDSQL},
}.prepare(db) }.prepare(db)
} }
@ -321,6 +326,26 @@ func (s *eventStatements) bulkSelectEventID(eventNIDs []types.EventNID) (map[typ
return results, nil return results, nil
} }
// bulkSelectEventNIDs returns a map from string event ID to numeric event ID.
// If an event ID is not in the database then it is omitted from the map.
func (s *eventStatements) bulkSelectEventNID(eventIDs []string) (map[string]types.EventNID, error) {
rows, err := s.bulkSelectEventNIDStmt.Query(pq.StringArray(eventIDs))
if err != nil {
return nil, err
}
defer rows.Close()
results := make(map[string]types.EventNID, len(eventIDs))
for rows.Next() {
var eventID string
var eventNID int64
if err = rows.Scan(&eventID, &eventNID); err != nil {
return nil, err
}
results[eventID] = types.EventNID(eventNID)
}
return results, nil
}
func eventNIDsAsArray(eventNIDs []types.EventNID) pq.Int64Array { func eventNIDsAsArray(eventNIDs []types.EventNID) pq.Int64Array {
nids := make([]int64, len(eventNIDs)) nids := make([]int64, len(eventNIDs))
for i := range eventNIDs { for i := range eventNIDs {

View file

@ -170,6 +170,11 @@ func (d *Database) EventStateKeyNIDs(eventStateKeys []string) (map[string]types.
return d.statements.bulkSelectEventStateKeyNID(eventStateKeys) return d.statements.bulkSelectEventStateKeyNID(eventStateKeys)
} }
// EventNIDs implements query.RoomQueryDatabase
func (d *Database) EventNIDs(eventIDs []string) (map[string]types.EventNID, error) {
return d.statements.bulkSelectEventNID(eventIDs)
}
// Events implements input.EventDatabase // Events implements input.EventDatabase
func (d *Database) Events(eventNIDs []types.EventNID) ([]types.Event, error) { func (d *Database) Events(eventNIDs []types.EventNID) ([]types.Event, error) {
eventJSONs, err := d.statements.bulkSelectEventJSON(eventNIDs) eventJSONs, err := d.statements.bulkSelectEventJSON(eventNIDs)