1package repositories
2
3import (
4	"database/sql"
5	"fmt"
6
7	lru "github.com/hashicorp/golang-lru"
8	"github.com/pkg/errors"
9
10	e "repodiff/entities"
11	repoSQL "repodiff/persistence/sql"
12)
13
14var cacheSingleton *lru.Cache
15
16const cacheSize = 1024
17
18type source struct {
19	db *sql.DB
20}
21
22func (s source) getOrCreateURLBranchID(url, branch string) (int16, error) {
23	url = protocolStrippedURL(url)
24	id, ok := cacheSingleton.Get(cacheKey(url, branch))
25	if ok {
26		return id.(int16), nil
27	}
28	val, err := s.getOrCreateURLBranchIDPersistence(url, branch)
29	if err != nil {
30		return 0, err
31	}
32	cacheSingleton.Add(cacheKey(url, branch), val)
33	return val, nil
34}
35
36func (s source) getOrCreateURLBranchIDPersistence(url, branch string) (int16, error) {
37	id, err := s.getIDByURLBranch(url, branch)
38	if err == nil {
39		return id, nil
40	}
41	s.insertIgnoreError(url, branch)
42	return s.getIDByURLBranch(url, branch)
43}
44
45func (s source) insertIgnoreError(url, branch string) {
46	repoSQL.SingleTransactionInsert(
47		s.db,
48		`INSERT INTO id_to_url_branch (
49			url,
50			branch
51		) VALUES (?, ?)`,
52		[][]interface{}{
53			[]interface{}{
54				url,
55				branch,
56			},
57		},
58	)
59}
60
61func (s source) getIDByURLBranch(url, branch string) (int16, error) {
62	var id *int16
63	repoSQL.Select(
64		s.db,
65		func(row *sql.Rows) {
66			id = new(int16)
67			row.Scan(id)
68		},
69		"SELECT id FROM id_to_url_branch WHERE url = ? AND branch = ?",
70		url,
71		branch,
72	)
73	if id == nil {
74		return 0, errors.New(fmt.Sprintf("No ID found for %s %s", url, branch))
75	}
76	return *id, nil
77}
78
79func (s source) GetURLBranchByID(id int16) (string, string, error) {
80	urlBranchPair, ok := cacheSingleton.Get(id)
81	if ok {
82		asSlice := urlBranchPair.([]string)
83		return asSlice[0], asSlice[1], nil
84	}
85	url, branch, err := s.getURLBranchByIDPersistence(id)
86	if err == nil {
87		cacheSingleton.Add(id, []string{url, branch})
88	}
89	return url, branch, err
90}
91
92func (s source) getURLBranchByIDPersistence(id int16) (string, string, error) {
93	url := ""
94	branch := ""
95	repoSQL.Select(
96		s.db,
97		func(row *sql.Rows) {
98			row.Scan(&url, &branch)
99		},
100		"SELECT url, branch FROM id_to_url_branch WHERE id = ?",
101		id,
102	)
103	if url == "" {
104		return "", "", errors.New(fmt.Sprintf("No matching records for ID %d", id))
105	}
106	return url, branch, nil
107}
108
109func (s source) DiffTargetToMapped(target e.DiffTarget) (e.MappedDiffTarget, error) {
110	upstream, errU := s.getOrCreateURLBranchID(
111		target.Upstream.URL,
112		target.Upstream.Branch,
113	)
114	downstream, errD := s.getOrCreateURLBranchID(
115		target.Downstream.URL,
116		target.Downstream.Branch,
117	)
118	if errU != nil || errD != nil {
119		return e.MappedDiffTarget{}, errors.New("Failed interacting with the database")
120	}
121	return e.MappedDiffTarget{
122		UpstreamTarget:   upstream,
123		DownstreamTarget: downstream,
124	}, nil
125}
126
127func NewSourceRepository() (source, error) {
128	db, err := repoSQL.GetDBConnectionPool()
129	return source{
130		db: db,
131	}, errors.Wrap(err, "Could not establish a database connection")
132}
133
134func cacheKey(url, branch string) string {
135	return fmt.Sprintf("%s:%s", url, branch)
136}
137
138func init() {
139	cacheSingleton, _ = lru.New(cacheSize)
140}
141