File size: 2,777 Bytes
ca7217f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package dbengine

import (
	"fmt"

	"gorm.io/gorm"

	"github.com/metatube-community/metatube-sdk-go/database"
	"github.com/metatube-community/metatube-sdk-go/model"
)

var _ DBEngine = (*engine)(nil)

type DBEngine interface {
	actorEngine
	movieEngine
	AutoMigrate() error
	Driver() string
	Version() (string, error)
}

type engine struct {
	db *gorm.DB
}

func New(db *gorm.DB) DBEngine {
	return &engine{db: db}
}

func (e *engine) DB() *gorm.DB {
	return e.db.Session(&gorm.Session{})
}

func (e *engine) Driver() string {
	return e.db.Name()
}

func (e *engine) AutoMigrate() error {
	if e.Driver() == database.Postgres {
		sqlStmts := []string{
			// Create case-insensitive collation.
			`CREATE COLLATION IF NOT EXISTS nocase (
			   provider = icu,
			   locale = 'und-u-ks-level2',
			   deterministic = FALSE
			 )`,
			// Create pg_trgm extension.
			`CREATE EXTENSION IF NOT EXISTS pg_trgm`,
		}
		for _, sql := range sqlStmts {
			if err := e.db.Exec(sql).Error; err != nil {
				return err
			}
		}
	}

	// Table auto migration.
	if err := e.db.AutoMigrate(
		&model.MovieInfo{},
		&model.ActorInfo{},
		&model.MovieReviewInfo{},
	); err != nil {
		return err
	}

	if e.Driver() == database.Postgres {
		buildNocaseIndexSQL := func(table, column string) string {
			const tmpl = `CREATE INDEX IF NOT EXISTS idx_%s_%s_nocase ON %s (%s COLLATE nocase)`
			return fmt.Sprintf(tmpl, table, column, table, column)
		}
		buildTrgmIndexSQL := func(table, column string) string {
			const tmpl = `CREATE INDEX IF NOT EXISTS idx_%s_%s_trgm ON %s USING gin (%s gin_trgm_ops)`
			return fmt.Sprintf(tmpl, table, column, table, column)
		}
		sqlStmts := []string{
			// Create indexes for nocase collation.
			buildNocaseIndexSQL(model.ActorMetadataTableName, "provider"),
			buildNocaseIndexSQL(model.ActorMetadataTableName, "id"),
			buildNocaseIndexSQL(model.ActorMetadataTableName, "name"),
			buildNocaseIndexSQL(model.MovieMetadataTableName, "provider"),
			buildNocaseIndexSQL(model.MovieMetadataTableName, "id"),
			buildNocaseIndexSQL(model.MovieMetadataTableName, "number"),
			// Create indexes for full-text search.
			buildTrgmIndexSQL(model.ActorMetadataTableName, "name"),
			buildTrgmIndexSQL(model.MovieMetadataTableName, "number"),
			buildTrgmIndexSQL(model.MovieMetadataTableName, "title"),
		}
		for _, sql := range sqlStmts {
			if err := e.db.Exec(sql).Error; err != nil {
				return err
			}
		}
	}
	return nil
}

func (e *engine) Version() (version string, err error) {
	switch e.Driver() {
	case database.Postgres:
		err = e.DB().Raw("SELECT version();").Scan(&version).Error
	case database.Sqlite:
		err = e.DB().Raw("SELECT sqlite_version();").Scan(&version).Error
	default:
		err = fmt.Errorf("unsupported DB type: %s", e.Driver())
	}
	return
}