From 34ef1356a8f978f4e667cb9d30e2ec284bd3d85a Mon Sep 17 00:00:00 2001 From: Toni Spets Date: Fri, 9 May 2025 10:10:32 +0300 Subject: [PATCH] WIP: Media table for direct media metadata --- bridgev2/database/database.go | 11 +++ bridgev2/database/media.go | 114 +++++++++++++++++++++++ bridgev2/database/upgrades/00-latest.sql | 8 ++ bridgev2/database/upgrades/23-media.sql | 9 ++ 4 files changed, 142 insertions(+) create mode 100644 bridgev2/database/media.go create mode 100644 bridgev2/database/upgrades/23-media.sql diff --git a/bridgev2/database/database.go b/bridgev2/database/database.go index f1789441a..b2b516639 100644 --- a/bridgev2/database/database.go +++ b/bridgev2/database/database.go @@ -34,6 +34,7 @@ type Database struct { UserPortal *UserPortalQuery BackfillTask *BackfillTaskQuery KV *KVQuery + Media *MediaQuery } type MetaMerger interface { @@ -48,6 +49,7 @@ type MetaTypes struct { Message MetaTypeCreator Reaction MetaTypeCreator UserLogin MetaTypeCreator + Media MetaTypeCreator } type blankMeta struct{} @@ -74,6 +76,9 @@ func New(bridgeID networkid.BridgeID, mt MetaTypes, db *dbutil.Database) *Databa if mt.UserLogin == nil { mt.UserLogin = blankMetaCreator } + if mt.Media == nil { + mt.Media = blankMetaCreator + } db.UpgradeTable = upgrades.Table return &Database{ Database: db, @@ -141,6 +146,12 @@ func New(bridgeID networkid.BridgeID, mt MetaTypes, db *dbutil.Database) *Databa BridgeID: bridgeID, Database: db, }, + Media: &MediaQuery{ + BridgeID: bridgeID, + QueryHelper: dbutil.MakeQueryHelper(db, func(_ *dbutil.QueryHelper[*Media]) *Media { + return &Media{} + }), + }, } } diff --git a/bridgev2/database/media.go b/bridgev2/database/media.go new file mode 100644 index 000000000..f64f1c5c8 --- /dev/null +++ b/bridgev2/database/media.go @@ -0,0 +1,114 @@ +// Copyright (c) 2025 Tulir Asokan +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package database + +import ( + "context" + "database/sql" + "database/sql/driver" + "encoding/base64" + "fmt" + + "go.mau.fi/util/dbutil" + + "maunium.net/go/mautrix/bridgev2/networkid" +) + +type MediaQuery struct { + BridgeID networkid.BridgeID + MetaType MetaTypeCreator + *dbutil.QueryHelper[*Media] +} + +type Media struct { + BridgeID networkid.BridgeID + ID networkid.MediaID + + Metadata any +} + +var _ driver.Value = (sqlMediaID)(nil) +var _ sql.Scanner = (sqlMediaID)(nil) + +type sqlMediaID networkid.MediaID + +func (id sqlMediaID) Scan(src any) (err error) { + var s string + switch v := src.(type) { + case string: + s = v + case []byte: + s = string(v) + default: + return fmt.Errorf("invalid sql type for media id: %T", v) + } + + id, err = base64.RawStdEncoding.DecodeString(s) + return +} + +func (id sqlMediaID) Value() (driver.Value, error) { + return base64.RawStdEncoding.EncodeToString(id), nil +} + +const ( + getMediaQuery = ` + SELECT bridge_id, id, metadata FROM media + ` + insertMediaQuery = ` + INSERT INTO media ( + bridge_id, id, metadata + ) + VALUES ($1, $2, $3) + ` + updateMediaQuery = ` + UPDATE media SET metadata=$3 + WHERE bridge_id=$1 AND id=$2 + ` + deleteMediaQuery = ` + DELETE FROM media WHERE bridge_id=$1 AND id=$2 + ` +) + +func (mq *MediaQuery) GetByID(ctx context.Context, mediaID networkid.MediaID) (*Media, error) { + return mq.QueryOne(ctx, getMediaQuery, mq.BridgeID, sqlMediaID(mediaID)) +} + +func (mq *MediaQuery) Insert(ctx context.Context, media *Media) (err error) { + ensureBridgeIDMatches(&media.BridgeID, mq.BridgeID) + _, err = mq.GetDB().Exec(ctx, insertMediaQuery, media.ensureHasMetadata(mq.MetaType).sqlVariables()...) + return +} + +func (mq *MediaQuery) Update(ctx context.Context, media *Media) error { + ensureBridgeIDMatches(&media.BridgeID, mq.BridgeID) + return mq.Exec(ctx, updateMediaQuery, media.ensureHasMetadata(mq.MetaType).sqlVariables()...) +} + +func (mq *MediaQuery) Delete(ctx context.Context, mediaID networkid.MediaID) error { + return mq.Exec(ctx, deleteMediaQuery, mq.BridgeID, sqlMediaID(mediaID)) +} + +func (m *Media) Scan(row dbutil.Scannable) (*Media, error) { + err := row.Scan( + &m.BridgeID, (*sqlMediaID)(&m.ID), dbutil.JSON{Data: m.Metadata}, + ) + return m, err +} + +func (m *Media) ensureHasMetadata(metaType MetaTypeCreator) *Media { + if m.Metadata == nil { + m.Metadata = metaType() + } + return m +} + +func (m *Media) sqlVariables() []any { + return []any{ + m.BridgeID, sqlMediaID(m.ID), dbutil.JSON{Data: m.Metadata}, + } +} diff --git a/bridgev2/database/upgrades/00-latest.sql b/bridgev2/database/upgrades/00-latest.sql index 4eea05bbb..aaf6afd18 100644 --- a/bridgev2/database/upgrades/00-latest.sql +++ b/bridgev2/database/upgrades/00-latest.sql @@ -215,3 +215,11 @@ CREATE TABLE kv_store ( PRIMARY KEY (bridge_id, key) ); + +CREATE TABLE media ( + bridge_id TEXT NOT NULL, + id TEXT NOT NULL, + metadata jsonb NOT NULL, + + PRIMARY KEY (bridge_id, id) +); diff --git a/bridgev2/database/upgrades/23-media.sql b/bridgev2/database/upgrades/23-media.sql new file mode 100644 index 000000000..6a206c7bb --- /dev/null +++ b/bridgev2/database/upgrades/23-media.sql @@ -0,0 +1,9 @@ +-- v23 (compatible with v9+): Add media table + +CREATE TABLE media ( + bridge_id TEXT NOT NULL, + id TEXT NOT NULL, + metadata jsonb NOT NULL, + + PRIMARY KEY (bridge_id, id) +);