Browse Source

feat(storage): reduce the number of SQL queries when fetching entry enclosures

Frédéric Guillot 1 năm trước cách đây
mục cha
commit
82a6fe64ae
2 tập tin đã thay đổi với 65 bổ sung11 xóa
  1. 45 0
      internal/storage/enclosure.go
  2. 20 11
      internal/storage/entry_query_builder.go

+ 45 - 0
internal/storage/enclosure.go

@@ -60,6 +60,51 @@ func (s *Storage) GetEnclosures(entryID int64) (model.EnclosureList, error) {
 	return enclosures, nil
 }
 
+func (s *Storage) GetEnclosuresForEntries(entryIDs []int64) (map[int64]model.EnclosureList, error) {
+	query := `
+		SELECT
+			id,
+			user_id,
+			entry_id,
+			url,
+			size,
+			mime_type,
+		    media_progression
+		FROM
+			enclosures
+		WHERE
+			entry_id = ANY($1)
+		ORDER BY id ASC
+	`
+
+	rows, err := s.db.Query(query, pq.Array(entryIDs))
+	if err != nil {
+		return nil, fmt.Errorf("store: unable to fetch enclosures: %w", err)
+	}
+	defer rows.Close()
+
+	enclosuresMap := make(map[int64]model.EnclosureList)
+	for rows.Next() {
+		var enclosure model.Enclosure
+		err := rows.Scan(
+			&enclosure.ID,
+			&enclosure.UserID,
+			&enclosure.EntryID,
+			&enclosure.URL,
+			&enclosure.Size,
+			&enclosure.MimeType,
+			&enclosure.MediaProgression,
+		)
+		if err != nil {
+			return nil, fmt.Errorf("store: unable to scan enclosure row: %w", err)
+		}
+
+		enclosuresMap[enclosure.EntryID] = append(enclosuresMap[enclosure.EntryID], &enclosure)
+	}
+
+	return enclosuresMap, nil
+}
+
 func (s *Storage) GetEnclosure(enclosureID int64) (*model.Enclosure, error) {
 	query := `
 		SELECT

+ 20 - 11
internal/storage/entry_query_builder.go

@@ -277,7 +277,6 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) {
 			e.created_at,
 			e.changed_at,
 			e.tags,
-			(SELECT true FROM enclosures WHERE entry_id=e.id LIMIT 1) as has_enclosure,
 			f.title as feed_title,
 			f.feed_url,
 			f.site_url,
@@ -319,10 +318,12 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) {
 	defer rows.Close()
 
 	entries := make(model.Entries, 0)
+	entryMap := make(map[int64]*model.Entry)
+	var entryIDs []int64
+
 	for rows.Next() {
 		var iconID sql.NullInt64
 		var tz string
-		var hasEnclosure sql.NullBool
 
 		entry := model.NewEntry()
 
@@ -344,7 +345,6 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) {
 			&entry.CreatedAt,
 			&entry.ChangedAt,
 			pq.Array(&entry.Tags),
-			&hasEnclosure,
 			&entry.Feed.Title,
 			&entry.Feed.FeedURL,
 			&entry.Feed.SiteURL,
@@ -368,20 +368,13 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) {
 			return nil, fmt.Errorf("store: unable to fetch entry row: %v", err)
 		}
 
-		if hasEnclosure.Valid && hasEnclosure.Bool && e.fetchEnclosures {
-			entry.Enclosures, err = e.store.GetEnclosures(entry.ID)
-			if err != nil {
-				return nil, fmt.Errorf("store: unable to fetch enclosures for entry #%d: %w", entry.ID, err)
-			}
-		}
-
 		if iconID.Valid {
 			entry.Feed.Icon.IconID = iconID.Int64
 		} else {
 			entry.Feed.Icon.IconID = 0
 		}
 
-		// Make sure that timestamp fields contains timezone information (API)
+		// Make sure that timestamp fields contain timezone information (API)
 		entry.Date = timezone.Convert(tz, entry.Date)
 		entry.CreatedAt = timezone.Convert(tz, entry.CreatedAt)
 		entry.ChangedAt = timezone.Convert(tz, entry.ChangedAt)
@@ -391,7 +384,23 @@ func (e *EntryQueryBuilder) GetEntries() (model.Entries, error) {
 		entry.Feed.UserID = entry.UserID
 		entry.Feed.Icon.FeedID = entry.FeedID
 		entry.Feed.Category.UserID = entry.UserID
+
 		entries = append(entries, entry)
+		entryMap[entry.ID] = entry
+		entryIDs = append(entryIDs, entry.ID)
+	}
+
+	if e.fetchEnclosures && len(entryIDs) > 0 {
+		enclosures, err := e.store.GetEnclosuresForEntries(entryIDs)
+		if err != nil {
+			return nil, fmt.Errorf("store: unable to fetch enclosures: %w", err)
+		}
+
+		for entryID, entryEnclosures := range enclosures {
+			if entry, exists := entryMap[entryID]; exists {
+				entry.Enclosures = entryEnclosures
+			}
+		}
 	}
 
 	return entries, nil