2020-12-01 20:06:49 +01:00
|
|
|
package bolt
|
|
|
|
|
2021-04-09 23:02:19 +02:00
|
|
|
import (
|
2021-05-15 14:23:47 +02:00
|
|
|
"bytes"
|
2021-05-08 19:25:00 +02:00
|
|
|
"encoding/json"
|
2021-05-07 12:08:34 +02:00
|
|
|
"fmt"
|
2021-05-08 19:25:00 +02:00
|
|
|
"github.com/ansible-semaphore/semaphore/db"
|
2021-04-09 23:02:19 +02:00
|
|
|
"github.com/ansible-semaphore/semaphore/util"
|
2021-05-07 12:08:34 +02:00
|
|
|
"go.etcd.io/bbolt"
|
2022-01-23 15:01:11 +01:00
|
|
|
"math/rand"
|
2021-05-08 19:25:00 +02:00
|
|
|
"reflect"
|
|
|
|
"sort"
|
2022-01-23 15:01:11 +01:00
|
|
|
"strconv"
|
2021-09-16 22:51:53 +02:00
|
|
|
"strings"
|
2022-11-18 23:23:30 +01:00
|
|
|
"sync"
|
2021-09-17 12:07:11 +02:00
|
|
|
"time"
|
2021-04-09 23:02:19 +02:00
|
|
|
)
|
2020-12-01 20:06:49 +01:00
|
|
|
|
2021-06-24 21:53:36 +02:00
|
|
|
const MaxID = 2147483647
|
2021-05-08 22:25:31 +02:00
|
|
|
|
|
|
|
type enumerable interface {
|
|
|
|
First() (key []byte, value []byte)
|
|
|
|
Next() (key []byte, value []byte)
|
|
|
|
}
|
|
|
|
|
2021-12-18 14:16:34 +01:00
|
|
|
type emptyEnumerable struct{}
|
2021-05-14 00:37:45 +02:00
|
|
|
|
|
|
|
func (d emptyEnumerable) First() (key []byte, value []byte) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d emptyEnumerable) Next() (key []byte, value []byte) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
2021-05-08 22:25:31 +02:00
|
|
|
|
2021-04-09 23:02:19 +02:00
|
|
|
type BoltDb struct {
|
2022-11-09 17:30:35 +01:00
|
|
|
Filename string
|
|
|
|
db *bbolt.DB
|
|
|
|
connections map[string]bool
|
2022-11-18 23:23:30 +01:00
|
|
|
mu sync.Mutex
|
2020-12-01 20:06:49 +01:00
|
|
|
}
|
|
|
|
|
2021-05-09 22:08:10 +02:00
|
|
|
type objectID interface {
|
|
|
|
ToBytes() []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
type intObjectID int
|
|
|
|
type strObjectID string
|
|
|
|
|
|
|
|
func (d intObjectID) ToBytes() []byte {
|
|
|
|
return []byte(fmt.Sprintf("%010d", d))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d strObjectID) ToBytes() []byte {
|
|
|
|
return []byte(d)
|
|
|
|
}
|
|
|
|
|
2022-02-03 08:05:13 +01:00
|
|
|
func makeBucketId(props db.ObjectProps, ids ...int) []byte {
|
2021-05-07 12:08:34 +02:00
|
|
|
n := len(ids)
|
2020-12-01 20:06:49 +01:00
|
|
|
|
2021-05-08 22:25:31 +02:00
|
|
|
id := props.TableName
|
2021-05-09 22:08:10 +02:00
|
|
|
|
|
|
|
if !props.IsGlobal {
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
id += fmt.Sprintf("_%010d", ids[i])
|
|
|
|
}
|
2021-05-07 12:08:34 +02:00
|
|
|
}
|
|
|
|
|
2021-05-08 19:25:00 +02:00
|
|
|
return []byte(id)
|
2021-05-07 12:08:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (d *BoltDb) Migrate() error {
|
|
|
|
return nil
|
2020-12-01 20:06:49 +01:00
|
|
|
}
|
|
|
|
|
2022-11-18 23:23:30 +01:00
|
|
|
func (d *BoltDb) Connect(token string) {
|
|
|
|
d.mu.Lock()
|
|
|
|
defer d.mu.Unlock()
|
|
|
|
|
2022-11-09 17:30:35 +01:00
|
|
|
if d.connections == nil {
|
|
|
|
d.connections = make(map[string]bool)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, exists := d.connections[token]; exists {
|
2023-08-28 10:53:49 +02:00
|
|
|
// Use for debugging
|
2023-08-29 00:51:04 +02:00
|
|
|
panic(fmt.Errorf("Connection " + token + " already exists"))
|
2022-11-09 17:30:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(d.connections) > 0 {
|
|
|
|
d.connections[token] = true
|
2022-11-18 23:23:30 +01:00
|
|
|
return
|
2022-11-09 17:30:35 +01:00
|
|
|
}
|
|
|
|
|
2021-05-14 13:47:32 +02:00
|
|
|
var filename string
|
|
|
|
if d.Filename == "" {
|
|
|
|
config, err := util.Config.GetDBConfig()
|
|
|
|
if err != nil {
|
2022-11-18 23:23:30 +01:00
|
|
|
panic(err)
|
2021-05-14 13:47:32 +02:00
|
|
|
}
|
2023-01-28 00:25:25 +01:00
|
|
|
filename = config.GetHostname()
|
2021-05-14 13:47:32 +02:00
|
|
|
} else {
|
|
|
|
filename = d.Filename
|
2021-04-09 23:02:19 +02:00
|
|
|
}
|
2021-05-14 13:47:32 +02:00
|
|
|
|
|
|
|
var err error
|
2021-09-17 12:05:04 +02:00
|
|
|
d.db, err = bbolt.Open(filename, 0666, &bbolt.Options{
|
|
|
|
Timeout: 5 * time.Second,
|
|
|
|
})
|
|
|
|
|
2021-04-09 23:02:19 +02:00
|
|
|
if err != nil {
|
2022-11-18 23:23:30 +01:00
|
|
|
panic(err)
|
2021-04-09 23:02:19 +02:00
|
|
|
}
|
2021-05-14 13:47:32 +02:00
|
|
|
|
2022-11-09 17:30:35 +01:00
|
|
|
d.connections[token] = true
|
|
|
|
}
|
|
|
|
|
2022-11-18 23:23:30 +01:00
|
|
|
func (d *BoltDb) Close(token string) {
|
|
|
|
d.mu.Lock()
|
|
|
|
defer d.mu.Unlock()
|
2022-11-09 17:30:35 +01:00
|
|
|
|
|
|
|
_, exists := d.connections[token]
|
|
|
|
|
|
|
|
if !exists {
|
2023-08-28 10:53:49 +02:00
|
|
|
// Use for debugging
|
2023-08-29 00:51:04 +02:00
|
|
|
panic(fmt.Errorf("can not close closed connection " + token))
|
2022-11-09 17:30:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(d.connections) > 1 {
|
|
|
|
delete(d.connections, token)
|
2022-11-18 23:23:30 +01:00
|
|
|
return
|
2022-11-09 17:30:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
err := d.db.Close()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
d.db = nil
|
|
|
|
delete(d.connections, token)
|
2021-04-09 23:02:19 +02:00
|
|
|
}
|
2020-12-01 20:06:49 +01:00
|
|
|
|
2022-11-18 23:23:30 +01:00
|
|
|
func (d *BoltDb) PermanentConnection() bool {
|
2022-11-09 17:30:35 +01:00
|
|
|
return false
|
2021-04-09 23:02:19 +02:00
|
|
|
}
|
2021-05-08 19:25:00 +02:00
|
|
|
|
2021-12-18 14:16:34 +01:00
|
|
|
func (d *BoltDb) IsInitialized() (initialized bool, err error) {
|
|
|
|
err = d.db.View(func(tx *bbolt.Tx) error {
|
|
|
|
k, _ := tx.Cursor().First()
|
|
|
|
initialized = k != nil
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-02-03 08:05:13 +01:00
|
|
|
func (d *BoltDb) getObject(bucketID int, props db.ObjectProps, objectID objectID, object interface{}) (err error) {
|
2021-05-08 19:25:00 +02:00
|
|
|
err = d.db.View(func(tx *bbolt.Tx) error {
|
2021-05-09 22:08:10 +02:00
|
|
|
b := tx.Bucket(makeBucketId(props, bucketID))
|
2021-05-08 19:25:00 +02:00
|
|
|
if b == nil {
|
|
|
|
return db.ErrNotFound
|
|
|
|
}
|
|
|
|
|
2021-05-09 22:08:10 +02:00
|
|
|
str := b.Get(objectID.ToBytes())
|
2021-05-08 19:25:00 +02:00
|
|
|
if str == nil {
|
|
|
|
return db.ErrNotFound
|
|
|
|
}
|
|
|
|
|
2021-05-15 13:27:42 +02:00
|
|
|
return unmarshalObject(str, object)
|
2021-05-08 19:25:00 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-09-16 22:51:53 +02:00
|
|
|
// getFieldNameByTagSuffix tries to find field by tag name and value in provided type.
|
2021-05-15 14:23:47 +02:00
|
|
|
// It returns error if field not found.
|
2021-09-16 22:51:53 +02:00
|
|
|
func getFieldNameByTagSuffix(t reflect.Type, tagName string, tagValueSuffix string) (string, error) {
|
2021-05-08 19:25:00 +02:00
|
|
|
n := t.NumField()
|
|
|
|
for i := 0; i < n; i++ {
|
2021-09-16 22:51:53 +02:00
|
|
|
if strings.HasSuffix(t.Field(i).Tag.Get(tagName), tagValueSuffix) {
|
2021-05-08 19:25:00 +02:00
|
|
|
return t.Field(i).Name, nil
|
|
|
|
}
|
|
|
|
}
|
2021-05-16 23:44:42 +02:00
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
if t.Field(i).Tag != "" || t.Field(i).Type.Kind() != reflect.Struct {
|
|
|
|
continue
|
|
|
|
}
|
2021-09-16 22:51:53 +02:00
|
|
|
str, err := getFieldNameByTagSuffix(t.Field(i).Type, tagName, tagValueSuffix)
|
2021-05-16 23:44:42 +02:00
|
|
|
if err == nil {
|
|
|
|
return str, nil
|
|
|
|
}
|
|
|
|
}
|
2021-05-15 14:23:47 +02:00
|
|
|
return "", fmt.Errorf("field not found")
|
2021-05-08 19:25:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func sortObjects(objects interface{}, sortBy string, sortInverted bool) error {
|
|
|
|
objectsValue := reflect.ValueOf(objects).Elem()
|
|
|
|
objType := objectsValue.Type().Elem()
|
2021-05-08 22:25:31 +02:00
|
|
|
|
2021-09-16 22:51:53 +02:00
|
|
|
fieldName, err := getFieldNameByTagSuffix(objType, "db", sortBy)
|
2021-05-08 19:25:00 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-12-18 14:16:34 +01:00
|
|
|
sort.SliceStable(objectsValue.Interface(), func(i, j int) bool {
|
2021-05-08 22:25:31 +02:00
|
|
|
valueI := objectsValue.Index(i).FieldByName(fieldName)
|
|
|
|
valueJ := objectsValue.Index(j).FieldByName(fieldName)
|
|
|
|
|
|
|
|
less := false
|
|
|
|
|
|
|
|
switch valueI.Kind() {
|
2021-05-15 14:23:47 +02:00
|
|
|
case reflect.Int,
|
|
|
|
reflect.Int8,
|
|
|
|
reflect.Int16,
|
|
|
|
reflect.Int32,
|
|
|
|
reflect.Int64,
|
|
|
|
reflect.Uint,
|
|
|
|
reflect.Uint8,
|
|
|
|
reflect.Uint16,
|
|
|
|
reflect.Uint32,
|
|
|
|
reflect.Uint64:
|
2021-05-08 22:25:31 +02:00
|
|
|
less = valueI.Int() < valueJ.Int()
|
2021-05-08 19:25:00 +02:00
|
|
|
case reflect.Float32:
|
|
|
|
case reflect.Float64:
|
2021-05-08 22:25:31 +02:00
|
|
|
less = valueI.Float() < valueJ.Float()
|
2021-05-08 19:25:00 +02:00
|
|
|
case reflect.String:
|
2021-05-08 22:25:31 +02:00
|
|
|
less = valueI.String() < valueJ.String()
|
2021-05-08 19:25:00 +02:00
|
|
|
}
|
2021-05-08 22:25:31 +02:00
|
|
|
|
|
|
|
if sortInverted {
|
|
|
|
less = !less
|
|
|
|
}
|
|
|
|
|
|
|
|
return less
|
2021-05-08 19:25:00 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-05-14 00:37:45 +02:00
|
|
|
func createObjectType(t reflect.Type) reflect.Type {
|
2021-05-13 23:17:16 +02:00
|
|
|
if t.Kind() == reflect.Ptr {
|
|
|
|
t = t.Elem()
|
|
|
|
}
|
|
|
|
|
|
|
|
n := t.NumField()
|
|
|
|
|
|
|
|
fields := make([]reflect.StructField, n)
|
|
|
|
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
f := t.Field(i)
|
|
|
|
tag := f.Tag.Get("db")
|
2021-05-14 00:37:45 +02:00
|
|
|
if tag != "" {
|
|
|
|
f.Tag = reflect.StructTag(`json:"` + tag + `"`)
|
|
|
|
} else {
|
|
|
|
if f.Type.Kind() == reflect.Struct {
|
|
|
|
f.Type = createObjectType(f.Type)
|
|
|
|
}
|
2021-05-13 23:17:16 +02:00
|
|
|
}
|
|
|
|
fields[i] = f
|
|
|
|
}
|
|
|
|
|
|
|
|
return reflect.StructOf(fields)
|
|
|
|
}
|
|
|
|
|
|
|
|
func unmarshalObject(data []byte, obj interface{}) error {
|
2021-05-14 00:37:45 +02:00
|
|
|
newType := createObjectType(reflect.TypeOf(obj))
|
2021-05-13 23:17:16 +02:00
|
|
|
ptr := reflect.New(newType).Interface()
|
|
|
|
|
|
|
|
err := json.Unmarshal(data, ptr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
value := reflect.ValueOf(ptr).Elem()
|
|
|
|
|
|
|
|
objValue := reflect.ValueOf(obj).Elem()
|
|
|
|
|
|
|
|
for i := 0; i < newType.NumField(); i++ {
|
|
|
|
objValue.Field(i).Set(value.Field(i))
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-05-14 00:37:45 +02:00
|
|
|
func copyObject(obj interface{}, newType reflect.Type) interface{} {
|
2021-05-13 23:17:16 +02:00
|
|
|
newValue := reflect.New(newType).Elem()
|
|
|
|
|
|
|
|
oldValue := reflect.ValueOf(obj)
|
|
|
|
|
|
|
|
for i := 0; i < newType.NumField(); i++ {
|
2021-05-14 00:37:45 +02:00
|
|
|
var v interface{}
|
|
|
|
if newValue.Field(i).Kind() == reflect.Struct &&
|
|
|
|
newValue.Field(i).Type().PkgPath() == "" {
|
|
|
|
v = copyObject(oldValue.Field(i).Interface(), newValue.Field(i).Type())
|
|
|
|
} else {
|
|
|
|
v = oldValue.Field(i).Interface()
|
|
|
|
}
|
|
|
|
newValue.Field(i).Set(reflect.ValueOf(v))
|
2021-05-13 23:17:16 +02:00
|
|
|
}
|
|
|
|
|
2021-05-14 00:37:45 +02:00
|
|
|
return newValue.Interface()
|
|
|
|
}
|
|
|
|
|
|
|
|
func marshalObject(obj interface{}) ([]byte, error) {
|
|
|
|
newType := createObjectType(reflect.TypeOf(obj))
|
|
|
|
return json.Marshal(copyObject(obj, newType))
|
2021-05-13 23:17:16 +02:00
|
|
|
}
|
|
|
|
|
2022-02-03 08:05:13 +01:00
|
|
|
func unmarshalObjects(rawData enumerable, props db.ObjectProps, params db.RetrieveQueryParams, filter func(interface{}) bool, objects interface{}) (err error) {
|
2021-05-08 19:25:00 +02:00
|
|
|
objectsValue := reflect.ValueOf(objects).Elem()
|
|
|
|
objType := objectsValue.Type().Elem()
|
|
|
|
|
2021-05-14 01:23:23 +02:00
|
|
|
objectsValue.Set(reflect.MakeSlice(objectsValue.Type(), 0, 0))
|
|
|
|
|
2021-05-13 00:56:31 +02:00
|
|
|
i := 0 // offset counter
|
2021-05-08 22:25:31 +02:00
|
|
|
n := 0 // number of added items
|
2021-05-08 19:25:00 +02:00
|
|
|
|
2021-05-08 22:25:31 +02:00
|
|
|
for k, v := rawData.First(); k != nil; k, v = rawData.Next() {
|
2021-05-13 00:56:31 +02:00
|
|
|
if params.Offset > 0 && i < params.Offset {
|
|
|
|
i++
|
2021-05-08 22:25:31 +02:00
|
|
|
continue
|
|
|
|
}
|
2021-05-08 19:25:00 +02:00
|
|
|
|
2021-05-13 21:45:54 +02:00
|
|
|
tmp := reflect.New(objType)
|
|
|
|
ptr := tmp.Interface()
|
2021-05-13 23:17:16 +02:00
|
|
|
err = unmarshalObject(v, ptr)
|
2021-05-13 21:45:54 +02:00
|
|
|
obj := reflect.ValueOf(ptr).Elem().Interface()
|
|
|
|
|
|
|
|
if err != nil {
|
2021-05-13 23:17:16 +02:00
|
|
|
return
|
2021-05-08 22:25:31 +02:00
|
|
|
}
|
2021-05-08 19:25:00 +02:00
|
|
|
|
2021-05-13 15:49:32 +02:00
|
|
|
if filter != nil {
|
|
|
|
if !filter(obj) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-13 21:45:54 +02:00
|
|
|
newObjectValues := reflect.Append(objectsValue, reflect.ValueOf(obj))
|
|
|
|
objectsValue.Set(newObjectValues)
|
2021-05-08 19:25:00 +02:00
|
|
|
|
2021-05-08 22:25:31 +02:00
|
|
|
n++
|
2021-05-08 19:25:00 +02:00
|
|
|
|
2021-05-14 13:47:32 +02:00
|
|
|
if params.Count > 0 && n > params.Count {
|
2021-05-08 22:25:31 +02:00
|
|
|
break
|
2021-05-08 19:25:00 +02:00
|
|
|
}
|
2021-05-08 22:25:31 +02:00
|
|
|
}
|
2021-05-08 19:25:00 +02:00
|
|
|
|
2021-05-09 22:08:10 +02:00
|
|
|
sortable := false
|
|
|
|
|
2021-05-08 22:25:31 +02:00
|
|
|
if params.SortBy != "" {
|
2021-05-09 22:08:10 +02:00
|
|
|
for _, v := range props.SortableColumns {
|
|
|
|
if v == params.SortBy {
|
|
|
|
sortable = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if sortable {
|
2021-05-08 22:25:31 +02:00
|
|
|
err = sortObjects(objects, params.SortBy, params.SortInverted)
|
|
|
|
}
|
2021-05-08 19:25:00 +02:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-09-09 14:41:41 +02:00
|
|
|
func (d *BoltDb) getObjectsTx(tx *bbolt.Tx, bucketID int, props db.ObjectProps, params db.RetrieveQueryParams, filter func(interface{}) bool, objects interface{}) error {
|
|
|
|
b := tx.Bucket(makeBucketId(props, bucketID))
|
|
|
|
var c enumerable
|
|
|
|
if b == nil {
|
|
|
|
c = emptyEnumerable{}
|
|
|
|
} else {
|
|
|
|
c = b.Cursor()
|
|
|
|
}
|
|
|
|
return unmarshalObjects(c, props, params, filter, objects)
|
|
|
|
}
|
|
|
|
|
2022-02-03 08:05:13 +01:00
|
|
|
func (d *BoltDb) getObjects(bucketID int, props db.ObjectProps, params db.RetrieveQueryParams, filter func(interface{}) bool, objects interface{}) error {
|
2021-05-08 22:25:31 +02:00
|
|
|
return d.db.View(func(tx *bbolt.Tx) error {
|
2023-09-09 14:41:41 +02:00
|
|
|
return d.getObjectsTx(tx, bucketID, props, params, filter, objects)
|
2021-05-08 22:25:31 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-02-03 08:05:13 +01:00
|
|
|
func (d *BoltDb) deleteObject(bucketID int, props db.ObjectProps, objectID objectID, tx *bbolt.Tx) error {
|
|
|
|
for _, u := range []db.ObjectProps{db.TemplateProps, db.EnvironmentProps, db.InventoryProps, db.RepositoryProps} {
|
2021-05-16 23:44:42 +02:00
|
|
|
inUse, err := d.isObjectInUse(bucketID, props, objectID, u)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if inUse {
|
|
|
|
return db.ErrInvalidOperation
|
|
|
|
}
|
2021-05-15 14:23:47 +02:00
|
|
|
}
|
|
|
|
|
2022-01-31 23:16:00 +01:00
|
|
|
fn := func(tx *bbolt.Tx) error {
|
2021-05-15 13:27:42 +02:00
|
|
|
b := tx.Bucket(makeBucketId(props, bucketID))
|
|
|
|
if b == nil {
|
|
|
|
return db.ErrNotFound
|
|
|
|
}
|
|
|
|
return b.Delete(objectID.ToBytes())
|
2022-01-31 23:16:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if tx != nil {
|
|
|
|
return fn(tx)
|
|
|
|
}
|
|
|
|
|
|
|
|
return d.db.Update(fn)
|
2021-05-15 13:27:42 +02:00
|
|
|
}
|
|
|
|
|
2023-09-09 14:41:41 +02:00
|
|
|
func (d *BoltDb) updateObjectTx(tx *bbolt.Tx, bucketID int, props db.ObjectProps, object interface{}) error {
|
|
|
|
b := tx.Bucket(makeBucketId(props, bucketID))
|
|
|
|
if b == nil {
|
|
|
|
return db.ErrNotFound
|
|
|
|
}
|
2021-05-16 23:44:42 +02:00
|
|
|
|
2023-09-09 14:41:41 +02:00
|
|
|
idFieldName, err := getFieldNameByTagSuffix(reflect.TypeOf(object), "db", props.PrimaryColumnName)
|
2021-05-16 23:44:42 +02:00
|
|
|
|
2023-09-09 14:41:41 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-05-08 22:25:31 +02:00
|
|
|
|
2023-09-09 14:41:41 +02:00
|
|
|
idValue := reflect.ValueOf(object).FieldByName(idFieldName)
|
|
|
|
|
|
|
|
var objID objectID
|
|
|
|
|
|
|
|
switch idValue.Kind() {
|
|
|
|
case reflect.Int,
|
|
|
|
reflect.Int8,
|
|
|
|
reflect.Int16,
|
|
|
|
reflect.Int32,
|
|
|
|
reflect.Int64,
|
|
|
|
reflect.Uint,
|
|
|
|
reflect.Uint8,
|
|
|
|
reflect.Uint16,
|
|
|
|
reflect.Uint32,
|
|
|
|
reflect.Uint64:
|
|
|
|
objID = intObjectID(idValue.Int())
|
|
|
|
case reflect.String:
|
|
|
|
objID = strObjectID(idValue.String())
|
|
|
|
}
|
2021-05-15 13:27:42 +02:00
|
|
|
|
2023-09-09 14:41:41 +02:00
|
|
|
if objID == nil {
|
|
|
|
return fmt.Errorf("unsupported ID type")
|
|
|
|
}
|
2021-05-15 13:27:42 +02:00
|
|
|
|
2023-09-09 14:41:41 +02:00
|
|
|
if b.Get(objID.ToBytes()) == nil {
|
|
|
|
return db.ErrNotFound
|
|
|
|
}
|
2021-05-15 13:27:42 +02:00
|
|
|
|
2023-09-09 14:41:41 +02:00
|
|
|
str, err := marshalObject(object)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-05-08 22:25:31 +02:00
|
|
|
|
2023-09-09 14:41:41 +02:00
|
|
|
return b.Put(objID.ToBytes(), str)
|
|
|
|
}
|
2021-05-08 22:25:31 +02:00
|
|
|
|
2023-09-09 14:41:41 +02:00
|
|
|
// updateObject updates data for object in database.
|
|
|
|
func (d *BoltDb) updateObject(bucketID int, props db.ObjectProps, object interface{}) error {
|
|
|
|
return d.db.Update(func(tx *bbolt.Tx) error {
|
|
|
|
return d.updateObjectTx(tx, bucketID, props, object)
|
2021-05-08 22:25:31 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-02-03 08:05:13 +01:00
|
|
|
func (d *BoltDb) createObject(bucketID int, props db.ObjectProps, object interface{}) (interface{}, error) {
|
2021-05-08 22:25:31 +02:00
|
|
|
err := d.db.Update(func(tx *bbolt.Tx) error {
|
2021-05-16 23:44:42 +02:00
|
|
|
b, err := tx.CreateBucketIfNotExists(makeBucketId(props, bucketID))
|
2021-05-08 22:25:31 +02:00
|
|
|
|
2021-05-16 23:44:42 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2021-05-08 22:25:31 +02:00
|
|
|
}
|
|
|
|
|
2021-05-13 21:45:54 +02:00
|
|
|
objPtr := reflect.ValueOf(&object).Elem()
|
2021-05-09 22:08:10 +02:00
|
|
|
|
2021-05-13 21:45:54 +02:00
|
|
|
tmpObj := reflect.New(objPtr.Elem().Type()).Elem()
|
|
|
|
tmpObj.Set(objPtr.Elem())
|
|
|
|
|
2022-01-24 21:14:30 +01:00
|
|
|
var objID objectID
|
2021-05-16 23:44:42 +02:00
|
|
|
|
2021-06-24 19:45:28 +02:00
|
|
|
if props.PrimaryColumnName != "" {
|
2022-01-24 21:14:30 +01:00
|
|
|
idFieldName, err2 := getFieldNameByTagSuffix(reflect.TypeOf(object), "db", props.PrimaryColumnName)
|
2021-05-16 23:44:42 +02:00
|
|
|
|
2022-01-24 21:14:30 +01:00
|
|
|
if err2 != nil {
|
|
|
|
return err2
|
2021-06-24 19:45:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
idValue := tmpObj.FieldByName(idFieldName)
|
|
|
|
|
|
|
|
switch idValue.Kind() {
|
|
|
|
case reflect.Int,
|
|
|
|
reflect.Int8,
|
|
|
|
reflect.Int16,
|
|
|
|
reflect.Int32,
|
|
|
|
reflect.Int64,
|
|
|
|
reflect.Uint,
|
|
|
|
reflect.Uint8,
|
|
|
|
reflect.Uint16,
|
|
|
|
reflect.Uint32,
|
|
|
|
reflect.Uint64:
|
2022-10-21 22:47:43 +02:00
|
|
|
if idValue.Int() == 0 {
|
|
|
|
id, err3 := b.NextSequence()
|
|
|
|
if err3 != nil {
|
|
|
|
return err3
|
|
|
|
}
|
|
|
|
if props.SortInverted {
|
|
|
|
id = MaxID - id
|
|
|
|
}
|
|
|
|
idValue.SetInt(int64(id))
|
2021-06-24 19:45:28 +02:00
|
|
|
}
|
2021-06-24 21:53:36 +02:00
|
|
|
|
2022-01-24 21:14:30 +01:00
|
|
|
objID = intObjectID(idValue.Int())
|
2021-06-24 19:45:28 +02:00
|
|
|
case reflect.String:
|
|
|
|
if idValue.String() == "" {
|
|
|
|
return fmt.Errorf("object ID can not be empty string")
|
|
|
|
}
|
2022-01-24 21:14:30 +01:00
|
|
|
objID = strObjectID(idValue.String())
|
2021-06-24 19:45:28 +02:00
|
|
|
case reflect.Invalid:
|
2022-01-24 21:14:30 +01:00
|
|
|
id, err3 := b.NextSequence()
|
|
|
|
if err3 != nil {
|
|
|
|
return err3
|
2021-05-09 22:08:10 +02:00
|
|
|
}
|
2022-01-24 21:14:30 +01:00
|
|
|
objID = intObjectID(id)
|
2021-06-24 19:45:28 +02:00
|
|
|
default:
|
|
|
|
return fmt.Errorf("unsupported ID type")
|
2021-05-09 22:08:10 +02:00
|
|
|
}
|
2021-06-24 19:45:28 +02:00
|
|
|
} else {
|
2021-05-14 00:37:45 +02:00
|
|
|
id, err2 := b.NextSequence()
|
|
|
|
if err2 != nil {
|
|
|
|
return err2
|
|
|
|
}
|
2021-06-24 21:53:36 +02:00
|
|
|
if props.SortInverted {
|
|
|
|
id = MaxID - id
|
|
|
|
}
|
2022-01-24 21:14:30 +01:00
|
|
|
objID = intObjectID(id)
|
2021-05-09 22:08:10 +02:00
|
|
|
}
|
2021-05-08 22:25:31 +02:00
|
|
|
|
2022-01-24 21:14:30 +01:00
|
|
|
if objID == nil {
|
2021-05-09 22:08:10 +02:00
|
|
|
return fmt.Errorf("object ID can not be nil")
|
|
|
|
}
|
2021-05-08 22:25:31 +02:00
|
|
|
|
2021-05-13 21:45:54 +02:00
|
|
|
objPtr.Set(tmpObj)
|
2021-05-16 23:44:42 +02:00
|
|
|
str, err := marshalObject(object)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2021-05-08 22:25:31 +02:00
|
|
|
}
|
|
|
|
|
2022-01-24 21:14:30 +01:00
|
|
|
return b.Put(objID.ToBytes(), str)
|
2021-05-08 22:25:31 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
return object, err
|
|
|
|
}
|
2022-01-23 15:01:11 +01:00
|
|
|
|
2024-02-11 20:52:14 +01:00
|
|
|
func (d *BoltDb) getIntegrationRefs(projectID int, objectProps db.ObjectProps, objectID int) (refs db.IntegrationReferrers, err error) {
|
|
|
|
refs.IntegrationExtractors, err = d.getReferringObjectByParentID(projectID, objectProps, objectID, db.IntegrationExtractorProps)
|
2023-07-03 01:41:13 +02:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-02-11 20:52:14 +01:00
|
|
|
func (d *BoltDb) getIntegrationExtractorRefs(integrationID int, objectProps db.ObjectProps, objectID int) (refs db.IntegrationExtractorReferrers, err error) {
|
|
|
|
refs.IntegrationMatchers, err = d.getReferringObjectByParentID(integrationID, objectProps, objectID, db.IntegrationMatcherProps)
|
2023-07-03 01:41:13 +02:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-02-11 20:52:14 +01:00
|
|
|
refs.IntegrationExtractValues, err = d.getReferringObjectByParentID(integrationID, objectProps, objectID, db.IntegrationExtractValueProps)
|
2023-07-03 01:41:13 +02:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-02-11 20:52:14 +01:00
|
|
|
func (d *BoltDb) getIntegrationExtractorChildrenRefs(extractorID int, objectProps db.ObjectProps, objectID int) (refs db.IntegrationExtractorChildReferrers, err error) {
|
|
|
|
refs.IntegrationExtractors, err = d.getReferringObjectByParentID(objectID, objectProps, extractorID, db.IntegrationExtractorProps)
|
2023-07-03 01:41:13 +02:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *BoltDb) getReferringObjectByParentID(parentID int, objProps db.ObjectProps, objID int, referringObjectProps db.ObjectProps) (referringObjs []db.ObjectReferrer, err error) {
|
|
|
|
referringObjs = make([]db.ObjectReferrer, 0)
|
|
|
|
|
|
|
|
var referringObjectOfType reflect.Value = reflect.New(reflect.SliceOf(referringObjectProps.Type))
|
2024-02-11 20:52:14 +01:00
|
|
|
err = d.getObjects(parentID, referringObjectProps, db.RetrieveQueryParams{}, func(referringObj interface{}) bool {
|
2023-07-03 01:41:13 +02:00
|
|
|
return isObjectReferredBy(objProps, intObjectID(objID), referringObj)
|
|
|
|
}, referringObjectOfType.Interface())
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < referringObjectOfType.Elem().Len(); i++ {
|
|
|
|
referringObjs = append(referringObjs, db.ObjectReferrer{
|
2024-02-11 20:52:14 +01:00
|
|
|
ID: int(referringObjectOfType.Elem().Index(i).FieldByName("ID").Int()),
|
2023-07-03 01:41:13 +02:00
|
|
|
Name: referringObjectOfType.Elem().Index(i).FieldByName("Name").String(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-02-03 08:05:13 +01:00
|
|
|
func (d *BoltDb) getObjectRefs(projectID int, objectProps db.ObjectProps, objectID int) (refs db.ObjectReferrers, err error) {
|
|
|
|
refs.Templates, err = d.getObjectRefsFrom(projectID, objectProps, intObjectID(objectID), db.TemplateProps)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
refs.Repositories, err = d.getObjectRefsFrom(projectID, objectProps, intObjectID(objectID), db.RepositoryProps)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
refs.Inventories, err = d.getObjectRefsFrom(projectID, objectProps, intObjectID(objectID), db.InventoryProps)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-02-03 10:19:35 +01:00
|
|
|
templates, err := d.getObjectRefsFrom(projectID, objectProps, intObjectID(objectID), db.ScheduleProps)
|
|
|
|
|
|
|
|
for _, st := range templates {
|
|
|
|
exists := false
|
|
|
|
for _, tpl := range refs.Templates {
|
|
|
|
if tpl.ID == st.ID {
|
|
|
|
exists = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if exists {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
refs.Templates = append(refs.Templates, st)
|
|
|
|
}
|
2022-02-03 08:05:13 +01:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *BoltDb) getObjectRefsFrom(projectID int, objProps db.ObjectProps, objID objectID, referringObjectProps db.ObjectProps) (referringObjs []db.ObjectReferrer, err error) {
|
2022-02-03 10:19:35 +01:00
|
|
|
referringObjs = make([]db.ObjectReferrer, 0)
|
2022-02-03 08:05:13 +01:00
|
|
|
_, err = objProps.GetReferringFieldsFrom(referringObjectProps.Type)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-02-03 10:19:35 +01:00
|
|
|
var referringObjects reflect.Value
|
2022-02-03 08:05:13 +01:00
|
|
|
|
2022-02-03 10:19:35 +01:00
|
|
|
if referringObjectProps.Type == db.ScheduleProps.Type {
|
|
|
|
schedules := make([]db.Schedule, 0)
|
|
|
|
err = d.getObjects(projectID, db.ScheduleProps, db.RetrieveQueryParams{}, func(referringObj interface{}) bool {
|
|
|
|
return isObjectReferredBy(objProps, objID, referringObj)
|
|
|
|
}, &schedules)
|
2022-02-03 08:05:13 +01:00
|
|
|
|
2022-02-03 10:19:35 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, schedule := range schedules {
|
|
|
|
var template db.Template
|
|
|
|
template, err = d.GetTemplate(projectID, schedule.TemplateID)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
referringObjs = append(referringObjs, db.ObjectReferrer{
|
|
|
|
ID: template.ID,
|
|
|
|
Name: template.Name,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
referringObjects = reflect.New(reflect.SliceOf(referringObjectProps.Type))
|
|
|
|
err = d.getObjects(projectID, referringObjectProps, db.RetrieveQueryParams{}, func(referringObj interface{}) bool {
|
|
|
|
return isObjectReferredBy(objProps, objID, referringObj)
|
|
|
|
}, referringObjects.Interface())
|
2022-02-03 08:05:13 +01:00
|
|
|
|
2022-02-03 10:19:35 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < referringObjects.Elem().Len(); i++ {
|
|
|
|
referringObjs = append(referringObjs, db.ObjectReferrer{
|
|
|
|
ID: int(referringObjects.Elem().Index(i).FieldByName("ID").Int()),
|
|
|
|
Name: referringObjects.Elem().Index(i).FieldByName("Name").String(),
|
|
|
|
})
|
|
|
|
}
|
2022-02-03 08:05:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func isObjectReferredBy(props db.ObjectProps, objID objectID, referringObj interface{}) bool {
|
|
|
|
if props.ReferringColumnSuffix == "" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
fieldName, err := getFieldNameByTagSuffix(reflect.TypeOf(referringObj), "db", props.ReferringColumnSuffix)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
f := reflect.ValueOf(referringObj).FieldByName(fieldName)
|
|
|
|
|
|
|
|
if f.IsZero() {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if f.Kind() == reflect.Ptr {
|
|
|
|
if f.IsNil() {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
f = f.Elem()
|
|
|
|
}
|
|
|
|
|
|
|
|
var fVal objectID
|
|
|
|
switch f.Kind() {
|
|
|
|
case reflect.Int,
|
|
|
|
reflect.Int8,
|
|
|
|
reflect.Int16,
|
|
|
|
reflect.Int32,
|
|
|
|
reflect.Int64,
|
|
|
|
reflect.Uint,
|
|
|
|
reflect.Uint8,
|
|
|
|
reflect.Uint16,
|
|
|
|
reflect.Uint32,
|
|
|
|
reflect.Uint64:
|
|
|
|
fVal = intObjectID(f.Int())
|
|
|
|
case reflect.String:
|
|
|
|
fVal = strObjectID(f.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
if fVal == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return bytes.Equal(fVal.ToBytes(), objID.ToBytes())
|
|
|
|
}
|
|
|
|
|
|
|
|
// isObjectInUse checks if objID associated with any object in foreignTableProps.
|
|
|
|
func (d *BoltDb) isObjectInUse(bucketID int, objProps db.ObjectProps, objID objectID, referringObjectProps db.ObjectProps) (inUse bool, err error) {
|
|
|
|
referringObjects := reflect.New(reflect.SliceOf(referringObjectProps.Type))
|
|
|
|
|
|
|
|
err = d.getObjects(bucketID, referringObjectProps, db.RetrieveQueryParams{}, func(referringObj interface{}) bool {
|
|
|
|
return isObjectReferredBy(objProps, objID, referringObj)
|
|
|
|
}, referringObjects.Interface())
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
inUse = referringObjects.Elem().Len() > 0
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2022-11-18 23:23:30 +01:00
|
|
|
func CreateTestStore() *BoltDb {
|
2022-01-23 15:01:11 +01:00
|
|
|
r := rand.New(rand.NewSource(time.Now().UTC().UnixNano()))
|
|
|
|
fn := "/tmp/test_semaphore_db_" + strconv.Itoa(r.Int())
|
|
|
|
store := BoltDb{
|
|
|
|
Filename: fn,
|
|
|
|
}
|
2022-11-18 23:23:30 +01:00
|
|
|
store.Connect("test")
|
|
|
|
return &store
|
2022-01-23 15:01:11 +01:00
|
|
|
}
|