Skip to content

Commit 930411a

Browse files
committed
fix(database): set default .dingo data dir
Signed-off-by: Chris Gianelloni <wolf31o2@blinklabs.io>
1 parent eb833d2 commit 930411a

File tree

14 files changed

+414
-48
lines changed

14 files changed

+414
-48
lines changed

PLUGIN_DEVELOPMENT.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,4 +276,24 @@ Test environment variables and YAML configuration:
276276

277277
```bash
278278
DINGO_DATABASE_BLOB_MYPLUGIN_OPTION1=value ./dingo --blob myplugin
279-
```
279+
```
280+
281+
## Programmatic Option Overrides (for tests)
282+
283+
When writing tests or programmatically constructing database instances you can override plugin options
284+
without importing plugin implementation packages directly by using the plugin registry helper:
285+
286+
```go
287+
// Set data-dir for the blob plugin to a per-test temp directory
288+
plugin.SetPluginOption(plugin.PluginTypeBlob, "badger", "data-dir", t.TempDir())
289+
290+
// Set data-dir for the metadata plugin
291+
plugin.SetPluginOption(plugin.PluginTypeMetadata, "sqlite", "data-dir", t.TempDir())
292+
```
293+
294+
The helper sets the plugin option's destination variable in the registry before plugin instantiation.
295+
If the requested option is not defined by the targeted plugin the call is non-fatal and returns nil,
296+
allowing tests to run regardless of which plugin implementation is selected.
297+
298+
Using `t.TempDir()` guarantees each test uses its own on-disk path and prevents concurrent tests from
299+
colliding on shared directories (for example the default `.dingo` Badger directory).

connmanager/connection_manager.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ const (
3636

3737
type connectionInfo struct {
3838
conn *ouroboros.Connection
39-
isInbound bool
4039
peerAddr string
40+
isInbound bool
4141
}
4242

4343
type peerConnectionState struct {
@@ -47,19 +47,19 @@ type peerConnectionState struct {
4747

4848
type ConnectionManager struct {
4949
connections map[ouroboros.ConnectionId]*connectionInfo
50+
metrics *connectionManagerMetrics
5051
config ConnectionManagerConfig
5152
connectionsMutex sync.Mutex
52-
metrics *connectionManagerMetrics
5353
}
5454

5555
type ConnectionManagerConfig struct {
56+
PromRegistry prometheus.Registerer
5657
Logger *slog.Logger
5758
EventBus *event.EventBus
5859
ConnClosedFunc ConnectionManagerConnClosedFunc
5960
Listeners []ListenerConfig
6061
OutboundConnOpts []ouroboros.ConnectionOptionFunc
6162
OutboundSourcePort uint
62-
PromRegistry prometheus.Registerer
6363
}
6464

6565
type connectionManagerMetrics struct {

database/database.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"io"
2020
"log/slog"
2121

22+
"github.com/blinklabs-io/dingo/database/plugin"
2223
"github.com/blinklabs-io/dingo/database/plugin/blob"
2324
"github.com/blinklabs-io/dingo/database/plugin/metadata"
2425
"github.com/prometheus/client_golang/prometheus"
@@ -119,6 +120,7 @@ func (d *Database) init() error {
119120
func New(
120121
config *Config,
121122
) (*Database, error) {
123+
var err error
122124
if config == nil {
123125
config = DefaultConfig
124126
}
@@ -132,10 +134,31 @@ func New(
132134
if configCopy.MetadataPlugin == "" {
133135
configCopy.MetadataPlugin = DefaultConfig.MetadataPlugin
134136
}
135-
// DataDir defaulting behavior:
137+
// Handle DataDir configuration for plugins:
136138
// - nil config → DefaultConfig.DataDir (".dingo" for persistence)
137139
// - empty DataDir → in-memory storage
138140
// - non-empty DataDir → persistent storage at specified path
141+
// NOTE: SetPluginOption mutates global plugin state, so DataDir is effectively
142+
// process-wide and not concurrency-safe. Multiple Database instances in the
143+
// same process will share and overwrite these options.
144+
err = plugin.SetPluginOption(
145+
plugin.PluginTypeBlob,
146+
configCopy.BlobPlugin,
147+
"data-dir",
148+
configCopy.DataDir,
149+
)
150+
if err != nil {
151+
return nil, err
152+
}
153+
err = plugin.SetPluginOption(
154+
plugin.PluginTypeMetadata,
155+
configCopy.MetadataPlugin,
156+
"data-dir",
157+
configCopy.DataDir,
158+
)
159+
if err != nil {
160+
return nil, err
161+
}
139162
blobDb, err := blob.New(
140163
configCopy.BlobPlugin,
141164
)

database/plugin/blob/aws/database.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ type BlobStoreS3 struct {
3535
startupCtx context.Context
3636
logger *S3Logger
3737
client *s3.Client
38+
startupCancel context.CancelFunc
3839
bucket string
3940
prefix string
4041
region string
41-
startupCancel context.CancelFunc
4242
timeout time.Duration
4343
}
4444

database/plugin/blob/badger/database.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ type BlobStoreBadger struct {
3434
promRegistry prometheus.Registerer
3535
db *badger.DB
3636
logger *slog.Logger
37-
dataDir string
38-
gcEnabled bool
39-
blockCacheSize uint64
40-
indexCacheSize uint64
4137
gcTicker *time.Ticker
4238
gcStopCh chan struct{}
39+
dataDir string
4340
gcWg sync.WaitGroup
41+
blockCacheSize uint64
42+
indexCacheSize uint64
43+
gcEnabled bool
4444
}
4545

4646
// New creates a new database
@@ -76,7 +76,7 @@ func New(opts ...BlobStoreBadgerOptionFunc) (*BlobStoreBadger, error) {
7676
return nil, fmt.Errorf("failed to read data dir: %w", err)
7777
}
7878
// Create data directory
79-
if err := os.MkdirAll(db.dataDir, fs.ModePerm); err != nil {
79+
if err := os.MkdirAll(db.dataDir, 0o755); err != nil {
8080
return nil, fmt.Errorf("failed to create data dir: %w", err)
8181
}
8282
}

database/plugin/blob/badger/plugin.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ func initCmdlineOptions() {
4343
cmdlineOptions.blockCacheSize = DefaultBlockCacheSize
4444
cmdlineOptions.indexCacheSize = DefaultIndexCacheSize
4545
cmdlineOptions.gcEnabled = true
46+
cmdlineOptions.dataDir = ".dingo"
4647
}
4748

4849
// Register plugin
@@ -59,7 +60,7 @@ func init() {
5960
Name: "data-dir",
6061
Type: plugin.PluginOptionTypeString,
6162
Description: "Data directory for badger storage",
62-
DefaultValue: "",
63+
DefaultValue: ".dingo",
6364
Dest: &(cmdlineOptions.dataDir),
6465
},
6566
{

database/plugin/blob/gcs/plugin_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ func TestCredentialValidation(t *testing.T) {
2828
tests := []struct {
2929
name string
3030
credentialsFile string
31-
expectError bool
3231
errorMessage string
32+
expectError bool
3333
}{
3434
{
3535
name: "valid credentials file",

database/plugin/metadata/sqlite/database.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ type MetadataStoreSqlite struct {
3939
db *gorm.DB
4040
logger *slog.Logger
4141
timerVacuum *time.Timer
42-
timerMutex sync.Mutex
4342
dataDir string
43+
timerMutex sync.Mutex
4444
closed bool
4545
}
4646

@@ -159,7 +159,7 @@ func (d *MetadataStoreSqlite) Start() error {
159159
return fmt.Errorf("failed to read data dir: %w", err)
160160
}
161161
// Create data directory
162-
if err := os.MkdirAll(d.dataDir, fs.ModePerm); err != nil {
162+
if err := os.MkdirAll(d.dataDir, 0o755); err != nil {
163163
return fmt.Errorf("failed to create data dir: %w", err)
164164
}
165165
}

database/plugin/metadata/sqlite/plugin.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ var (
3131
func initCmdlineOptions() {
3232
cmdlineOptionsMutex.Lock()
3333
defer cmdlineOptionsMutex.Unlock()
34-
cmdlineOptions.dataDir = ""
34+
cmdlineOptions.dataDir = ".dingo"
3535
}
3636

3737
// Register plugin
@@ -48,7 +48,7 @@ func init() {
4848
Name: "data-dir",
4949
Type: plugin.PluginOptionTypeString,
5050
Description: "Data directory for sqlite storage",
51-
DefaultValue: "",
51+
DefaultValue: ".dingo",
5252
Dest: &(cmdlineOptions.dataDir),
5353
},
5454
},

database/plugin/plugin.go

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,173 @@ func StartPlugin(pluginType PluginType, pluginName string) (Plugin, error) {
6363

6464
return p, nil
6565
}
66+
67+
// SetPluginOption sets the value of a named option for a plugin entry. This
68+
// is used by callers that need to programmatically override plugin defaults
69+
// (for example to set data-dir before starting a plugin). It returns an error
70+
// if the plugin or option is not found or if the value type is incompatible.
71+
// NOTE: This function accesses the global pluginEntries slice without
72+
// synchronization. It should only be called during initialization or in
73+
// single-threaded contexts to avoid race conditions.
74+
// NOTE: This function writes directly to plugin option destinations (e.g.,
75+
// cmdlineOptions fields) without acquiring the plugin's cmdlineOptionsMutex.
76+
// It must be called before any plugin instantiation to avoid data races with
77+
// concurrent reads in NewFromCmdlineOptions.
78+
func SetPluginOption(
79+
pluginType PluginType,
80+
pluginName string,
81+
optionName string,
82+
value any,
83+
) error {
84+
for i := range pluginEntries {
85+
p := &pluginEntries[i]
86+
if p.Type != pluginType || p.Name != pluginName {
87+
continue
88+
}
89+
for _, opt := range p.Options {
90+
if opt.Name != optionName {
91+
continue
92+
}
93+
// Perform a type-checked assignment into the Dest pointer
94+
switch opt.Type {
95+
case PluginOptionTypeString:
96+
v, ok := value.(string)
97+
if !ok {
98+
return fmt.Errorf(
99+
"invalid type for option %s: expected string",
100+
optionName,
101+
)
102+
}
103+
if opt.Dest == nil {
104+
return fmt.Errorf(
105+
"nil destination for option %s",
106+
optionName,
107+
)
108+
}
109+
dest, ok := opt.Dest.(*string)
110+
if !ok {
111+
return fmt.Errorf(
112+
"invalid destination type for option %s: expected *string",
113+
optionName,
114+
)
115+
}
116+
if dest == nil {
117+
return fmt.Errorf(
118+
"nil destination pointer for option %s",
119+
optionName,
120+
)
121+
}
122+
*dest = v
123+
return nil
124+
case PluginOptionTypeBool:
125+
v, ok := value.(bool)
126+
if !ok {
127+
return fmt.Errorf(
128+
"invalid type for option %s: expected bool",
129+
optionName,
130+
)
131+
}
132+
if opt.Dest == nil {
133+
return fmt.Errorf(
134+
"nil destination for option %s",
135+
optionName,
136+
)
137+
}
138+
dest, ok := opt.Dest.(*bool)
139+
if !ok {
140+
return fmt.Errorf(
141+
"invalid destination type for option %s: expected *bool",
142+
optionName,
143+
)
144+
}
145+
if dest == nil {
146+
return fmt.Errorf(
147+
"nil destination pointer for option %s",
148+
optionName,
149+
)
150+
}
151+
*dest = v
152+
return nil
153+
case PluginOptionTypeInt:
154+
v, ok := value.(int)
155+
if !ok {
156+
return fmt.Errorf(
157+
"invalid type for option %s: expected int",
158+
optionName,
159+
)
160+
}
161+
if opt.Dest == nil {
162+
return fmt.Errorf(
163+
"nil destination for option %s",
164+
optionName,
165+
)
166+
}
167+
dest, ok := opt.Dest.(*int)
168+
if !ok {
169+
return fmt.Errorf(
170+
"invalid destination type for option %s: expected *int",
171+
optionName,
172+
)
173+
}
174+
if dest == nil {
175+
return fmt.Errorf(
176+
"nil destination pointer for option %s",
177+
optionName,
178+
)
179+
}
180+
*dest = v
181+
return nil
182+
case PluginOptionTypeUint:
183+
// accept uint64 or int
184+
switch tv := value.(type) {
185+
case uint64:
186+
if opt.Dest == nil {
187+
return fmt.Errorf("nil destination for option %s", optionName)
188+
}
189+
dest, ok := opt.Dest.(*uint64)
190+
if !ok {
191+
return fmt.Errorf("invalid destination type for option %s: expected *uint64", optionName)
192+
}
193+
if dest == nil {
194+
return fmt.Errorf("nil destination pointer for option %s", optionName)
195+
}
196+
*dest = tv
197+
return nil
198+
case int:
199+
if tv < 0 {
200+
return fmt.Errorf("invalid value for option %s: negative int", optionName)
201+
}
202+
if opt.Dest == nil {
203+
return fmt.Errorf("nil destination for option %s", optionName)
204+
}
205+
dest, ok := opt.Dest.(*uint64)
206+
if !ok {
207+
return fmt.Errorf("invalid destination type for option %s: expected *uint64", optionName)
208+
}
209+
if dest == nil {
210+
return fmt.Errorf("nil destination pointer for option %s", optionName)
211+
}
212+
*dest = uint64(tv)
213+
return nil
214+
default:
215+
return fmt.Errorf("invalid type for option %s: expected uint64 or int", optionName)
216+
}
217+
default:
218+
return fmt.Errorf(
219+
"unknown plugin option type %d for option %s",
220+
opt.Type,
221+
optionName,
222+
)
223+
}
224+
}
225+
// Option not found for this plugin: treat as non-fatal. This allows
226+
// callers to attempt to set options that may not exist for all
227+
// implementations (for example `data-dir` may not be relevant).
228+
return nil
229+
}
230+
return fmt.Errorf(
231+
"plugin %s of type %s not found",
232+
pluginName,
233+
PluginTypeName(pluginType),
234+
)
235+
}

0 commit comments

Comments
 (0)