Files
dislate/internals/guilddb/sqlite.go
2024-08-14 14:21:10 -03:00

501 lines
11 KiB
Go

package guilddb
import (
"database/sql"
"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 channel-groups (
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 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 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(`
SELECT (GuildID, ID, Language) FROM channel-groups
WHERE "GuildID" = $1 AND "Channels" LIKE "%$2%"
`, guildID, channelID).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 nil
}
var ids []string
for _, c := range g {
ids = append(ids, c.ID)
}
slices.Sort(ids)
r, err := db.sql.Exec(`
INSERT INTO channel-groups (GuildID, Channels)
VALUES ($1, $2)
`, g[0].GuildID, strings.Join(ids, ","))
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, "\"ID\" LIKE \""+c.ID+"\"")
}
slices.Sort(ids)
r, err := db.sql.Exec(
fmt.Sprintf(`
UPDATE channel-groups
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, "\"ID\" LIKE \""+c.ID+"\"")
}
slices.Sort(ids)
r, err := db.sql.Exec(
fmt.Sprintf(`
DELETE FROM channel-groups
WHERE %s AND "GuildID" = $1
`, 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 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
}