Files
dislate/internals/guilddb/sqlite.go
2024-08-20 15:23:41 -03:00

508 lines
11 KiB
Go

package guilddb
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"slices"
"strings"
"dislate/internals/translator/lang"
_ "github.com/tursodatabase/go-libsql"
)
type SQLiteDB struct {
sql *sql.DB
}
func NewSQLiteDB(file string) (*SQLiteDB, error) {
db, err := sql.Open("libsql", file)
if err != nil {
return &SQLiteDB{}, err
}
return &SQLiteDB{db}, nil
}
func (db *SQLiteDB) Close() error {
return db.sql.Close()
}
func (db *SQLiteDB) Prepare() error {
if _, err := db.sql.Exec(`
CREATE TABLE IF NOT EXISTS guilds (
ID text NOT NULL,
PRIMARY KEY(ID)
);
`); err != nil {
return errors.Join(ErrInternal, err)
}
if _, err := db.sql.Exec(`
CREATE TABLE IF NOT EXISTS channels (
GuildID text NOT NULL,
ID text NOT NULL,
Language text NOT NULL,
PRIMARY KEY(ID, GuildID),
FOREIGN KEY(GuildID) REFERENCES guilds(ID)
);
CREATE TABLE IF NOT EXISTS channelGroups (
GuildID text NOT NULL,
Channels text NOT NULL,
PRIMARY KEY(Channels, GuildID),
FOREIGN KEY(GuildID) REFERENCES guilds(ID)
);
`); err != nil {
return errors.Join(ErrInternal, err)
}
if _, err := db.sql.Exec(`
CREATE TABLE IF NOT EXISTS messages (
GuildID text NOT NULL,
ChannelID text NOT NULL,
ID text NOT NULL,
Language text NOT NULL,
OriginChannelID text,
OriginID text,
PRIMARY KEY(ID, ChannelID, GuildID),
FOREIGN KEY(ChannelID) REFERENCES channels(ID),
FOREIGN KEY(GuildID) REFERENCES guilds(ID),
FOREIGN KEY(OriginID) REFERENCES messages(ID),
FOREIGN KEY(OriginChannelID) REFERENCES channels(ID)
);
`); err != nil {
return errors.Join(ErrInternal, err)
}
return nil
}
func (db *SQLiteDB) Message(guildID, channelID, messageID string) (Message, error) {
return db.selectMessage(`
WHERE "GuildID" = $1 AND "ChannelID" = $2 AND "ID" = $3
`, guildID, channelID, messageID)
}
func (db *SQLiteDB) MessagesWithOrigin(guildID, originChannelID, originID string) ([]Message, error) {
return db.selectMessages(`
WHERE "GuildID" = $1 AND "OriginChannelID" = $2 AND "OriginID" = $3
`, guildID, originChannelID, originID)
}
func (db *SQLiteDB) MessageWithOriginByLang(
guildID, originChannelID, originID string,
language lang.Language,
) (Message, error) {
return db.selectMessage(`
WHERE "GuildID" = $1 AND "OriginChannelID" = $2 AND "OriginID" = $3 AND "Language" = $4
`, guildID, originChannelID, originID, language)
}
func (db *SQLiteDB) MessageInsert(m Message) error {
_, err := db.Channel(m.GuildID, m.ChannelID)
if errors.Is(err, ErrNotFound) {
return errors.Join(
ErrPreconditionFailed,
fmt.Errorf("Channel %s doesn't exists in the database", m.ChannelID),
)
} else if err != nil {
return errors.Join(
ErrInternal,
errors.New("Failed to check if Channel exists in the database"),
err,
)
}
r, err := db.sql.Exec(`
INSERT OR IGNORE INTO messages (GuildID, ChannelID, ID, Language, OriginChannelID, OriginID)
VALUES ($1, $2, $3, $4, $5, $6)
`, m.GuildID, m.ChannelID, m.ID, m.Language, m.OriginChannelID, m.OriginID)
if err != nil {
return errors.Join(ErrInternal, err)
} else if rows, _ := r.RowsAffected(); rows == 0 {
return ErrNoAffect
}
return nil
}
func (db *SQLiteDB) MessageUpdate(m Message) error {
r, err := db.sql.Exec(`
UPDATE messages
SET Language = $1, OriginChannelID = $2, OriginID = $3
WHERE "GuildID" = $4 AND "ChannelID" = $5 AND "ID" = $6
`, m.Language,
m.OriginChannelID,
m.OriginID,
m.GuildID,
m.ChannelID,
m.ID,
)
if err != nil {
return errors.Join(ErrInternal, err)
} else if rows, _ := r.RowsAffected(); rows == 0 {
return ErrNoAffect
}
return nil
}
func (db *SQLiteDB) MessageDelete(m Message) error {
_, err := db.sql.Exec(`
DELETE channels
WHERE "GuildID" = $1 AND "OriginChannelID" = $2 AND "OriginID" = $3
`, m.GuildID, m.ChannelID, m.ID)
if err != nil {
return errors.Join(ErrInternal, err)
}
r, err := db.sql.Exec(`
DELETE channels
WHERE "GuildID" = $1 AND "ChannelID" = $2 AND "ID" = $3
`, m.GuildID, m.ChannelID, m.ID)
if err != nil {
return errors.Join(ErrInternal, err)
} else if rows, _ := r.RowsAffected(); rows == 0 {
return ErrNoAffect
}
return nil
}
func (db *SQLiteDB) selectMessage(query string, args ...any) (Message, error) {
var m Message
err := db.sql.QueryRow(fmt.Sprintf(`
SELECT GuildID, ChannelID, ID, Language, OriginChannelID, OriginID FROM messages
%s
`, query), args...).
Scan(&m.GuildID, &m.ChannelID, &m.ID, &m.Language, &m.OriginChannelID, &m.OriginID)
if errors.Is(err, sql.ErrNoRows) {
return m, errors.Join(ErrNotFound, err)
} else if err != nil {
return m, errors.Join(ErrInternal, err)
}
return m, nil
}
func (db *SQLiteDB) selectMessages(query string, args ...any) ([]Message, error) {
r, err := db.sql.Query(fmt.Sprintf(`
SELECT GuildID, ChannelID, ID, Language, OriginChannelID, OriginID FROM messages
%s
`, query), args...)
if err != nil {
return []Message{}, errors.Join(ErrInternal, err)
}
var ms []Message
for r.Next() {
var m Message
err = r.Scan(&m.GuildID, &m.ChannelID, &m.ID, &m.Language, &m.OriginChannelID, &m.OriginID)
if err != nil {
return ms, errors.Join(
ErrInternal,
fmt.Errorf("Query: %s\nArguments: %v", query, args),
err,
)
}
ms = append(ms, m)
}
if len(ms) == 0 {
return ms, errors.Join(
ErrNotFound,
fmt.Errorf("Query: %s\nArguments: %v", query, args),
)
}
return ms, err
}
func (db *SQLiteDB) Channel(guildID, ID string) (Channel, error) {
return db.selectChannel(`
WHERE "GuildID" = $1 AND "ID" = $2
`, guildID, ID)
}
func (db *SQLiteDB) ChannelInsert(c Channel) error {
r, err := db.sql.Exec(`
INSERT OR IGNORE INTO channels (GuildID, ID, Language)
VALUES ($1, $2, $3)
`, c.GuildID, c.ID, c.Language)
if err != nil {
return errors.Join(ErrInternal, err)
} else if rows, _ := r.RowsAffected(); rows == 0 {
return ErrNoAffect
}
return nil
}
func (db *SQLiteDB) ChannelUpdate(c Channel) error {
r, err := db.sql.Exec(`
UPDATE channels
SET Language = $1
WHERE "GuildID" = $2 AND "ID" = $3
`, c.Language, c.GuildID, c.ID)
if err != nil {
return errors.Join(ErrInternal, err)
} else if rows, _ := r.RowsAffected(); rows == 0 {
return ErrNoAffect
}
return nil
}
func (db *SQLiteDB) ChannelDelete(c Channel) error {
r, err := db.sql.Exec(`
DELETE channels
WHERE "GuildID" = $1 AND "ID" = $2
`, c.ID, c.ID)
if err != nil {
return errors.Join(ErrInternal, err)
} else if rows, _ := r.RowsAffected(); rows == 0 {
return ErrNoAffect
}
return nil
}
func (db *SQLiteDB) ChannelGroup(guildID, channelID string) (ChannelGroup, error) {
var g string
err := db.sql.QueryRow(fmt.Sprintf(`
SELECT Channels FROM channelGroups, json_each(Channels)
WHERE "GuildID" = $1 AND json_each.value='%s';
`, channelID), guildID).Scan(&g)
if errors.Is(err, sql.ErrNoRows) {
return ChannelGroup{}, errors.Join(ErrNotFound, err)
} else if err != nil {
return ChannelGroup{}, errors.Join(ErrInternal, err)
}
ids := strings.Split(g, ",")
if !slices.IsSorted(ids) {
return ChannelGroup{}, errors.Join(
ErrInvalidObject,
fmt.Errorf("Channel in database is invalid, ids are not sorted: %s", ids),
)
}
for i, v := range ids {
ids[i] = fmt.Sprintf("\"ID\" = %s", v)
}
cs, err := db.selectChannels(fmt.Sprintf(`
WHERE %s AND "GuildID" = $1
`, strings.Join(ids, " OR ")), guildID)
if errors.Is(err, ErrNotFound) || len(cs) != len(ids) {
return ChannelGroup{}, errors.Join(
ErrPreconditionFailed,
fmt.Errorf("ChannelGroup has Channels that doesn't exist in the database, group: %s", ids),
err,
)
} else if err != nil {
return ChannelGroup{}, errors.Join(ErrInternal, err)
}
return cs, nil
}
func (db *SQLiteDB) ChannelGroupInsert(g ChannelGroup) error {
if len(g) == 0 {
return ErrNoAffect
}
var ids []string
for _, c := range g {
ids = append(ids, c.ID)
}
slices.Sort(ids)
j, err := json.Marshal(ids)
if err != nil {
return errors.Join(ErrInternal, err)
}
r, err := db.sql.Exec(fmt.Sprintf(`
INSERT OR IGNORE INTO channelGroups (GuildID, Channels)
VALUES ($1, json('%s'))
`, string(j)), g[0].GuildID)
if err != nil {
return errors.Join(ErrInternal, err)
} else if rows, _ := r.RowsAffected(); rows == 0 {
return ErrNoAffect
}
return nil
}
func (db *SQLiteDB) ChannelGroupUpdate(g ChannelGroup) error {
if len(g) != 0 {
return nil
}
var ids, idsq []string
for _, c := range g {
ids = append(ids, c.ID)
idsq = append(idsq, "json_each.value='"+c.ID+"'")
}
slices.Sort(ids)
r, err := db.sql.Exec(
fmt.Sprintf(`
UPDATE channelGroups, json_each(Channels)
SET Channels = $1
WHERE %s AND "GuildID" = $2
`, strings.Join(idsq, " OR ")),
strings.Join(ids, ","),
g[0].GuildID,
)
if err != nil {
return errors.Join(ErrInternal, err)
} else if rows, _ := r.RowsAffected(); rows == 0 {
return ErrNoAffect
}
return nil
}
func (db *SQLiteDB) ChannelGroupDelete(g ChannelGroup) error {
if len(g) != 0 {
return nil
}
var ids, idsq []string
for _, c := range g {
ids = append(ids, c.ID)
idsq = append(idsq, "json_each.value='"+c.ID+"'")
}
slices.Sort(ids)
r, err := db.sql.Exec(
fmt.Sprintf(`
DELETE FROM channelGroups, json_each(Channels)
WHERE %s AND "GuildID" = $2
`, strings.Join(idsq, " OR ")),
g[0].GuildID,
)
if err != nil {
return errors.Join(ErrInternal, err)
} else if rows, _ := r.RowsAffected(); rows == 0 {
return ErrNoAffect
}
return nil
}
func (db *SQLiteDB) selectChannel(query string, args ...any) (Channel, error) {
var c Channel
err := db.sql.QueryRow(fmt.Sprintf(`
SELECT GuildID, ID, Language FROM channels
%s
`, query), args...).Scan(&c.GuildID, &c.ID, &c.Language)
if errors.Is(err, sql.ErrNoRows) {
return c, errors.Join(ErrNotFound, err)
} else if err != nil {
return c, errors.Join(ErrInternal, err)
}
return c, nil
}
func (db *SQLiteDB) selectChannels(query string, args ...any) ([]Channel, error) {
r, err := db.sql.Query(fmt.Sprintf(`
SELECT GuildID, ID, Language FROM channels
%s
`, query), args...)
if err != nil {
return []Channel{}, errors.Join(ErrInternal, err)
}
var cs []Channel
for r.Next() {
var c Channel
err = r.Scan(&c.GuildID, &c.ID, &c.Language)
if err != nil {
return cs, errors.Join(
ErrInternal,
fmt.Errorf("Query: %s\nArguments: %v", query, args),
err,
)
}
cs = append(cs, c)
}
if len(cs) == 0 {
return cs, errors.Join(
ErrNotFound,
fmt.Errorf("Query: %s\nArguments: %v", query, args),
)
}
return cs, err
}
func (db *SQLiteDB) Guild(ID string) (Guild, error) {
var g Guild
if err := db.sql.QueryRow(`
SELECT "ID" FROM guilds
WHERE "ID" = $1
`, ID).Scan(g.ID); err != nil && errors.Is(err, sql.ErrNoRows) {
return Guild{}, errors.Join(ErrNotFound, err)
} else if err != nil {
return Guild{}, errors.Join(ErrInternal, err)
}
return g, nil
}
func (db *SQLiteDB) GuildInsert(g Guild) error {
r, err := db.sql.Exec(`
INSERT OR IGNORE INTO guilds (ID)
VALUES ($1)
`, g.ID)
if err != nil {
return errors.Join(ErrInternal, err)
} else if rows, _ := r.RowsAffected(); rows == 0 {
return ErrNoAffect
}
return nil
}
func (db *SQLiteDB) GuildDelete(g Guild) error {
r, err := db.sql.Exec(`
DELETE FROM guilds
WHERE "ID" = $1
`, g.ID)
if err != nil {
return errors.Join(ErrInternal, err)
} else if rows, _ := r.RowsAffected(); rows == 0 {
return ErrNoAffect
}
return nil
}