2020-01-23 11:51:10 -06:00
// Copyright 2018 New Vector 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.
package postgres
import (
"context"
"database/sql"
2020-05-21 08:40:13 -05:00
"github.com/matrix-org/dendrite/internal"
2020-09-15 05:17:46 -05:00
"github.com/matrix-org/dendrite/internal/sqlutil"
2020-05-14 10:11:37 -05:00
"github.com/matrix-org/dendrite/syncapi/storage/tables"
2020-01-23 11:51:10 -06:00
"github.com/matrix-org/dendrite/syncapi/types"
"github.com/matrix-org/gomatrixserverlib"
)
const outputRoomEventsTopologySchema = `
-- Stores output room events received from the roomserver .
CREATE TABLE IF NOT EXISTS syncapi_output_room_events_topology (
-- The event ID for the event .
event_id TEXT PRIMARY KEY ,
-- The place of the event in the room ' s topology . This can usually be determined
-- from the event ' s depth .
topological_position BIGINT NOT NULL ,
2020-05-01 05:01:34 -05:00
stream_position BIGINT NOT NULL ,
2020-01-23 11:51:10 -06:00
-- The ' room_id ' key for the event .
room_id TEXT NOT NULL
) ;
-- The topological order will be used in events selection and ordering
2020-05-01 05:01:34 -05:00
CREATE UNIQUE INDEX IF NOT EXISTS syncapi_event_topological_position_idx ON syncapi_output_room_events_topology ( topological_position , stream_position , room_id ) ;
2020-01-23 11:51:10 -06:00
`
const insertEventInTopologySQL = "" +
2020-05-01 05:01:34 -05:00
"INSERT INTO syncapi_output_room_events_topology (event_id, topological_position, room_id, stream_position)" +
" VALUES ($1, $2, $3, $4)" +
2021-01-20 14:43:20 -06:00
" ON CONFLICT (topological_position, stream_position, room_id) DO UPDATE SET event_id = $1" +
" RETURNING topological_position"
2020-01-23 11:51:10 -06:00
const selectEventIDsInRangeASCSQL = "" +
"SELECT event_id FROM syncapi_output_room_events_topology" +
2020-05-15 10:27:34 -05:00
" WHERE room_id = $1 AND (" +
2020-05-01 05:01:34 -05:00
"(topological_position > $2 AND topological_position < $3) OR" +
2022-03-18 05:40:01 -05:00
"(topological_position = $4 AND stream_position >= $5)" +
2020-05-15 10:27:34 -05:00
") ORDER BY topological_position ASC, stream_position ASC LIMIT $6"
2020-01-23 11:51:10 -06:00
const selectEventIDsInRangeDESCSQL = "" +
"SELECT event_id FROM syncapi_output_room_events_topology" +
2020-05-15 10:27:34 -05:00
" WHERE room_id = $1 AND (" +
2020-05-01 05:01:34 -05:00
"(topological_position > $2 AND topological_position < $3) OR" +
"(topological_position = $4 AND stream_position <= $5)" +
2020-05-15 10:27:34 -05:00
") ORDER BY topological_position DESC, stream_position DESC LIMIT $6"
2020-01-23 11:51:10 -06:00
const selectPositionInTopologySQL = "" +
2020-05-01 06:41:38 -05:00
"SELECT topological_position, stream_position FROM syncapi_output_room_events_topology" +
2020-01-23 11:51:10 -06:00
" WHERE event_id = $1"
2020-05-01 05:01:34 -05:00
// Select the max topological position for the room, then sort by stream position and take the highest,
// returning both topological and stream positions.
2020-01-23 11:51:10 -06:00
const selectMaxPositionInTopologySQL = "" +
2020-05-01 05:01:34 -05:00
"SELECT topological_position, stream_position FROM syncapi_output_room_events_topology" +
" WHERE topological_position=(" +
"SELECT MAX(topological_position) FROM syncapi_output_room_events_topology WHERE room_id=$1" +
") ORDER BY stream_position DESC LIMIT 1"
2020-01-23 11:51:10 -06:00
2022-03-18 05:40:01 -05:00
const selectStreamToTopologicalPositionAscSQL = "" +
"SELECT topological_position FROM syncapi_output_room_events_topology WHERE room_id = $1 AND stream_position >= $2 ORDER BY topological_position ASC LIMIT 1;"
const selectStreamToTopologicalPositionDescSQL = "" +
"SELECT topological_position FROM syncapi_output_room_events_topology WHERE room_id = $1 AND stream_position <= $2 ORDER BY topological_position DESC LIMIT 1;"
2020-01-23 11:51:10 -06:00
type outputRoomEventsTopologyStatements struct {
2022-03-18 05:40:01 -05:00
insertEventInTopologyStmt * sql . Stmt
selectEventIDsInRangeASCStmt * sql . Stmt
selectEventIDsInRangeDESCStmt * sql . Stmt
selectPositionInTopologyStmt * sql . Stmt
selectMaxPositionInTopologyStmt * sql . Stmt
selectStreamToTopologicalPositionAscStmt * sql . Stmt
selectStreamToTopologicalPositionDescStmt * sql . Stmt
2020-01-23 11:51:10 -06:00
}
2020-05-14 10:11:37 -05:00
func NewPostgresTopologyTable ( db * sql . DB ) ( tables . Topology , error ) {
s := & outputRoomEventsTopologyStatements { }
_ , err := db . Exec ( outputRoomEventsTopologySchema )
2020-01-23 11:51:10 -06:00
if err != nil {
2020-05-14 10:11:37 -05:00
return nil , err
2020-01-23 11:51:10 -06:00
}
if s . insertEventInTopologyStmt , err = db . Prepare ( insertEventInTopologySQL ) ; err != nil {
2020-05-14 10:11:37 -05:00
return nil , err
2020-01-23 11:51:10 -06:00
}
if s . selectEventIDsInRangeASCStmt , err = db . Prepare ( selectEventIDsInRangeASCSQL ) ; err != nil {
2020-05-14 10:11:37 -05:00
return nil , err
2020-01-23 11:51:10 -06:00
}
if s . selectEventIDsInRangeDESCStmt , err = db . Prepare ( selectEventIDsInRangeDESCSQL ) ; err != nil {
2020-05-14 10:11:37 -05:00
return nil , err
2020-01-23 11:51:10 -06:00
}
if s . selectPositionInTopologyStmt , err = db . Prepare ( selectPositionInTopologySQL ) ; err != nil {
2020-05-14 10:11:37 -05:00
return nil , err
2020-01-23 11:51:10 -06:00
}
if s . selectMaxPositionInTopologyStmt , err = db . Prepare ( selectMaxPositionInTopologySQL ) ; err != nil {
2020-05-14 10:11:37 -05:00
return nil , err
2020-01-23 11:51:10 -06:00
}
2022-03-18 05:40:01 -05:00
if s . selectStreamToTopologicalPositionAscStmt , err = db . Prepare ( selectStreamToTopologicalPositionAscSQL ) ; err != nil {
return nil , err
}
if s . selectStreamToTopologicalPositionDescStmt , err = db . Prepare ( selectStreamToTopologicalPositionDescSQL ) ; err != nil {
return nil , err
}
2020-05-14 10:11:37 -05:00
return s , nil
2020-01-23 11:51:10 -06:00
}
2020-05-14 10:11:37 -05:00
// InsertEventInTopology inserts the given event in the room's topology, based
2020-01-23 11:51:10 -06:00
// on the event's depth.
2020-05-14 10:11:37 -05:00
func ( s * outputRoomEventsTopologyStatements ) InsertEventInTopology (
ctx context . Context , txn * sql . Tx , event * gomatrixserverlib . HeaderedEvent , pos types . StreamPosition ,
2021-01-20 14:43:20 -06:00
) ( topoPos types . StreamPosition , err error ) {
err = sqlutil . TxStmt ( txn , s . insertEventInTopologyStmt ) . QueryRowContext (
2020-05-01 05:01:34 -05:00
ctx , event . EventID ( ) , event . Depth ( ) , event . RoomID ( ) , pos ,
2021-01-20 14:43:20 -06:00
) . Scan ( & topoPos )
2020-01-23 11:51:10 -06:00
return
}
2020-05-14 10:11:37 -05:00
// SelectEventIDsInRange selects the IDs of events which positions are within a
2020-01-23 11:51:10 -06:00
// given range in a given room's topological order.
// Returns an empty slice if no events match the given range.
2020-05-14 10:11:37 -05:00
func ( s * outputRoomEventsTopologyStatements ) SelectEventIDsInRange (
2020-05-14 11:30:16 -05:00
ctx context . Context , txn * sql . Tx , roomID string , minDepth , maxDepth , maxStreamPos types . StreamPosition ,
2020-01-23 11:51:10 -06:00
limit int , chronologicalOrder bool ,
) ( eventIDs [ ] string , err error ) {
// Decide on the selection's order according to whether chronological order
// is requested or not.
var stmt * sql . Stmt
if chronologicalOrder {
2022-04-08 11:53:24 -05:00
stmt = sqlutil . TxStmt ( txn , s . selectEventIDsInRangeASCStmt )
2020-01-23 11:51:10 -06:00
} else {
2022-04-08 11:53:24 -05:00
stmt = sqlutil . TxStmt ( txn , s . selectEventIDsInRangeDESCStmt )
2020-01-23 11:51:10 -06:00
}
// Query the event IDs.
2020-05-14 11:30:16 -05:00
rows , err := stmt . QueryContext ( ctx , roomID , minDepth , maxDepth , maxDepth , maxStreamPos , limit )
2020-01-23 11:51:10 -06:00
if err == sql . ErrNoRows {
// If no event matched the request, return an empty slice.
return [ ] string { } , nil
} else if err != nil {
return
}
2020-05-21 08:40:13 -05:00
defer internal . CloseAndLogIfError ( ctx , rows , "selectEventIDsInRange: rows.close() failed" )
2020-01-23 11:51:10 -06:00
// Return the IDs.
var eventID string
for rows . Next ( ) {
if err = rows . Scan ( & eventID ) ; err != nil {
return
}
eventIDs = append ( eventIDs , eventID )
}
2020-02-11 08:12:21 -06:00
return eventIDs , rows . Err ( )
2020-01-23 11:51:10 -06:00
}
2020-05-14 10:11:37 -05:00
// SelectPositionInTopology returns the position of a given event in the
2020-01-23 11:51:10 -06:00
// topology of the room it belongs to.
2020-05-14 10:11:37 -05:00
func ( s * outputRoomEventsTopologyStatements ) SelectPositionInTopology (
ctx context . Context , txn * sql . Tx , eventID string ,
2020-05-01 06:41:38 -05:00
) ( pos , spos types . StreamPosition , err error ) {
err = s . selectPositionInTopologyStmt . QueryRowContext ( ctx , eventID ) . Scan ( & pos , & spos )
2020-01-23 11:51:10 -06:00
return
}
2022-03-18 05:40:01 -05:00
// SelectStreamToTopologicalPosition returns the closest position of a given event
// in the topology of the room it belongs to from the given stream position.
func ( s * outputRoomEventsTopologyStatements ) SelectStreamToTopologicalPosition (
ctx context . Context , txn * sql . Tx , roomID string , streamPos types . StreamPosition , backwardOrdering bool ,
) ( topoPos types . StreamPosition , err error ) {
if backwardOrdering {
err = s . selectStreamToTopologicalPositionDescStmt . QueryRowContext ( ctx , roomID , streamPos ) . Scan ( & topoPos )
} else {
err = s . selectStreamToTopologicalPositionAscStmt . QueryRowContext ( ctx , roomID , streamPos ) . Scan ( & topoPos )
}
return
}
2020-05-14 10:11:37 -05:00
func ( s * outputRoomEventsTopologyStatements ) SelectMaxPositionInTopology (
ctx context . Context , txn * sql . Tx , roomID string ,
2020-05-01 05:01:34 -05:00
) ( pos types . StreamPosition , spos types . StreamPosition , err error ) {
err = s . selectMaxPositionInTopologyStmt . QueryRowContext ( ctx , roomID ) . Scan ( & pos , & spos )
2020-01-23 11:51:10 -06:00
return
}