github.com/altipla-consulting/ravendb-go-client@v0.1.3/document_store.go (about) 1 package ravendb 2 3 import ( 4 "crypto/tls" 5 "crypto/x509" 6 "strings" 7 "sync" 8 "time" 9 ) 10 11 // Note: Java's IDocumentStore is DocumentStore 12 // Note: Java's DocumentStoreBase is folded into DocumentStore 13 14 // DocumentStore represents a database 15 type DocumentStore struct { 16 // from DocumentStoreBase 17 onBeforeStore []func(*BeforeStoreEventArgs) 18 onAfterSaveChanges []func(*AfterSaveChangesEventArgs) 19 20 onBeforeDelete []func(*BeforeDeleteEventArgs) 21 onBeforeQuery []func(*BeforeQueryEventArgs) 22 // TODO: there's no way to register for this event 23 onSessionCreated []func(*SessionCreatedEventArgs) 24 subscriptions *DocumentSubscriptions 25 26 disposed bool 27 conventions *DocumentConventions 28 urls []string // urls for HTTP endopoints of server nodes 29 initialized bool 30 Certificate *tls.Certificate 31 TrustStore *x509.Certificate 32 database string // name of the database 33 34 // maps database name to DatabaseChanges. Must be protected with mutex 35 databaseChanges map[string]*DatabaseChanges 36 37 // Note: access must be protected with mu 38 // Lazy.Value is **EvictItemsFromCacheBasedOnChanges 39 aggressiveCacheChanges map[string]*evictItemsFromCacheBasedOnChanges 40 41 // maps database name to its RequestsExecutor 42 // access must be protected with mu 43 // TODO: in Java is ConcurrentMap<String, RequestExecutor> requestExecutors 44 // so must protect access with mutex and use case-insensitive lookup 45 requestsExecutors map[string]*RequestExecutor 46 47 multiDbHiLo *MultiDatabaseHiLoIDGenerator 48 maintenanceOperationExecutor *MaintenanceOperationExecutor 49 operationExecutor *OperationExecutor 50 identifier string 51 aggressiveCachingUsed bool 52 53 afterClose []func(*DocumentStore) 54 beforeClose []func(*DocumentStore) 55 56 mu sync.Mutex 57 } 58 59 // methods from DocumentStoreBase 60 61 // GetConventions returns DocumentConventions 62 func (s *DocumentStore) GetConventions() *DocumentConventions { 63 if s.conventions == nil { 64 s.conventions = NewDocumentConventions() 65 } 66 return s.conventions 67 } 68 69 // SetConventions sets DocumentConventions 70 func (s *DocumentStore) SetConventions(conventions *DocumentConventions) { 71 s.assertNotInitialized("conventions") 72 s.conventions = conventions 73 } 74 75 // Subscriptions returns DocumentSubscriptions which allows subscribing to changes in store 76 func (s *DocumentStore) Subscriptions() *DocumentSubscriptions { 77 return s.subscriptions 78 } 79 80 // GetUrls returns urls of all RavenDB nodes 81 func (s *DocumentStore) GetUrls() []string { 82 return s.urls 83 } 84 85 // SetUrls sets initial urls of RavenDB nodes 86 func (s *DocumentStore) SetUrls(urls []string) { 87 panicIf(len(urls) == 0, "urls is empty") 88 s.assertNotInitialized("urls") 89 for i, s := range urls { 90 urls[i] = strings.TrimSuffix(s, "/") 91 } 92 s.urls = urls 93 } 94 95 func (s *DocumentStore) ensureNotClosed() error { 96 if s.disposed { 97 return newIllegalStateError("The document store has already been disposed and cannot be used") 98 } 99 return nil 100 } 101 102 // AddBeforeStoreStoreListener registers a function that will be called before storing ab entity. 103 // It'll be registered with every new session. 104 // Returns listener id that can be passed to RemoveBeforeStoreListener to unregister 105 // the listener. 106 func (s *DocumentStore) AddBeforeStoreListener(handler func(*BeforeStoreEventArgs)) int { 107 id := len(s.onBeforeStore) 108 s.onBeforeStore = append(s.onBeforeStore, handler) 109 return id 110 111 } 112 113 // RemoveBeforeStoreListener removes a listener given id returned by AddBeforeStoreListener 114 func (s *DocumentStore) RemoveBeforeStoreListener(handlerID int) { 115 s.onBeforeStore[handlerID] = nil 116 } 117 118 // AddAfterSaveChangesListener registers a function that will be called before saving changes. 119 // It'll be registered with every new session. 120 // Returns listener id that can be passed to RemoveAfterSaveChangesListener to unregister 121 // the listener. 122 func (s *DocumentStore) AddAfterSaveChangesListener(handler func(*AfterSaveChangesEventArgs)) int { 123 s.onAfterSaveChanges = append(s.onAfterSaveChanges, handler) 124 return len(s.onAfterSaveChanges) - 1 125 } 126 127 // RemoveAfterSaveChangesListener removes a listener given id returned by AddAfterSaveChangesListener 128 func (s *DocumentStore) RemoveAfterSaveChangesListener(handlerID int) { 129 s.onAfterSaveChanges[handlerID] = nil 130 } 131 132 // AddBeforeDeleteListener registers a function that will be called before deleting an entity. 133 // It'll be registered with every new session. 134 // Returns listener id that can be passed to RemoveBeforeDeleteListener to unregister 135 // the listener. 136 func (s *DocumentStore) AddBeforeDeleteListener(handler func(*BeforeDeleteEventArgs)) int { 137 s.onBeforeDelete = append(s.onBeforeDelete, handler) 138 return len(s.onBeforeDelete) - 1 139 } 140 141 // RemoveBeforeDeleteListener removes a listener given id returned by AddBeforeDeleteListener 142 func (s *DocumentStore) RemoveBeforeDeleteListener(handlerID int) { 143 s.onBeforeDelete[handlerID] = nil 144 } 145 146 // AddBeforeQueryListener registers a function that will be called before running a query. 147 // It allows customizing query via DocumentQueryCustomization. 148 // It'll be registered with every new session. 149 // Returns listener id that can be passed to RemoveBeforeQueryListener to unregister 150 // the listener. 151 func (s *DocumentStore) AddBeforeQueryListener(handler func(*BeforeQueryEventArgs)) int { 152 s.onBeforeQuery = append(s.onBeforeQuery, handler) 153 return len(s.onBeforeQuery) - 1 154 } 155 156 // RemoveBeforeQueryListener removes a listener given id returned by AddBeforeQueryListener 157 func (s *DocumentStore) RemoveBeforeQueryListener(handlerID int) { 158 s.onBeforeQuery[handlerID] = nil 159 } 160 161 func (s *DocumentStore) registerEvents(session *InMemoryDocumentSessionOperations) { 162 // TODO: unregister those events? 163 for _, handler := range s.onBeforeStore { 164 if handler != nil { 165 session.AddBeforeStoreListener(handler) 166 } 167 } 168 169 for _, handler := range s.onAfterSaveChanges { 170 if handler != nil { 171 session.AddAfterSaveChangesListener(handler) 172 } 173 } 174 175 for _, handler := range s.onBeforeDelete { 176 if handler != nil { 177 session.AddBeforeDeleteListener(handler) 178 } 179 } 180 181 for _, handler := range s.onBeforeQuery { 182 if handler != nil { 183 session.AddBeforeQueryListener(handler) 184 } 185 } 186 } 187 188 func (s *DocumentStore) afterSessionCreated(session *InMemoryDocumentSessionOperations) { 189 for _, handler := range s.onSessionCreated { 190 if handler != nil { 191 args := &SessionCreatedEventArgs{ 192 Session: session, 193 } 194 handler(args) 195 } 196 } 197 } 198 199 func (s *DocumentStore) assertInitialized() error { 200 if !s.initialized { 201 return newIllegalStateError("DocumentStore must be initialized") 202 } 203 return nil 204 } 205 206 func (s *DocumentStore) assertNotInitialized(property string) { 207 panicIf(s.initialized, "You cannot set '%s' after the document store has been initialized.", property) 208 } 209 210 func (s *DocumentStore) GetDatabase() string { 211 return s.database 212 } 213 214 func (s *DocumentStore) SetDatabase(database string) { 215 s.assertNotInitialized("database") 216 s.database = database 217 } 218 219 func (s *DocumentStore) AggressivelyCache(database string) (CancelFunc, error) { 220 return s.AggressivelyCacheForDatabase(time.Hour*24, database) 221 } 222 223 func newDocumentStore() *DocumentStore { 224 s := &DocumentStore{ 225 requestsExecutors: map[string]*RequestExecutor{}, 226 conventions: NewDocumentConventions(), 227 databaseChanges: map[string]*DatabaseChanges{}, 228 aggressiveCacheChanges: map[string]*evictItemsFromCacheBasedOnChanges{}, 229 } 230 s.subscriptions = newDocumentSubscriptions(s) 231 return s 232 } 233 234 func NewDocumentStore(urls []string, database string) *DocumentStore { 235 res := newDocumentStore() 236 if len(urls) > 0 { 237 res.SetUrls(urls) 238 } 239 if database != "" { 240 res.SetDatabase(database) 241 } 242 return res 243 } 244 245 // Get an identifier of the store. For debugging / testing. 246 func (s *DocumentStore) GetIdentifier() string { 247 if s.identifier != "" { 248 return s.identifier 249 } 250 251 if len(s.urls) == 0 { 252 return "" 253 } 254 255 if s.database != "" { 256 return strings.Join(s.urls, ",") + " (DB: " + s.database + ")" 257 } 258 259 return strings.Join(s.urls, ",") 260 } 261 262 func (s *DocumentStore) SetIdentifier(identifier string) { 263 s.identifier = identifier 264 } 265 266 // Close closes the Store 267 func (s *DocumentStore) Close() { 268 if s.disposed { 269 redbg("DocumentStore.Close: already disposed\n") 270 return 271 } 272 redbg("DocumentStore.Close\n") 273 274 for _, fn := range s.beforeClose { 275 fn(s) 276 } 277 s.beforeClose = nil 278 279 for _, evict := range s.aggressiveCacheChanges { 280 evict.Close() 281 } 282 283 for _, changes := range s.databaseChanges { 284 changes.Close() 285 } 286 287 if s.multiDbHiLo != nil { 288 s.multiDbHiLo.ReturnUnusedRange() 289 } 290 291 if s.Subscriptions() != nil { 292 _ = s.Subscriptions().Close() 293 } 294 295 s.disposed = true 296 297 for _, fn := range s.afterClose { 298 fn(s) 299 } 300 s.afterClose = nil 301 302 for _, re := range s.requestsExecutors { 303 re.Close() 304 } 305 } 306 307 // OpenSession opens a new session to document Store. 308 // If database is not given, we'll use store's database name 309 func (s *DocumentStore) OpenSession(database string) (*DocumentSession, error) { 310 sessionOptions := &SessionOptions{ 311 Database: database, 312 } 313 return s.OpenSessionWithOptions(sessionOptions) 314 } 315 316 func (s *DocumentStore) OpenSessionWithOptions(options *SessionOptions) (*DocumentSession, error) { 317 if err := s.assertInitialized(); err != nil { 318 return nil, err 319 } 320 if err := s.ensureNotClosed(); err != nil { 321 return nil, err 322 } 323 324 sessionID := NewUUID().String() 325 databaseName := options.Database 326 if databaseName == "" { 327 databaseName = s.GetDatabase() 328 } 329 requestExecutor := options.RequestExecutor 330 if requestExecutor == nil { 331 requestExecutor = s.GetRequestExecutor(databaseName) 332 } 333 session := NewDocumentSession(databaseName, s, sessionID, requestExecutor) 334 s.registerEvents(session.InMemoryDocumentSessionOperations) 335 s.afterSessionCreated(session.InMemoryDocumentSessionOperations) 336 return session, nil 337 } 338 339 func (s *DocumentStore) ExecuteIndex(task *IndexCreationTask, database string) error { 340 if err := s.assertInitialized(); err != nil { 341 return err 342 } 343 return task.Execute(s, s.conventions, database) 344 } 345 346 func (s *DocumentStore) ExecuteIndexes(tasks []*IndexCreationTask, database string) error { 347 if err := s.assertInitialized(); err != nil { 348 return err 349 } 350 indexesToAdd := indexCreationCreateIndexesToAdd(tasks, s.conventions) 351 352 op := NewPutIndexesOperation(indexesToAdd...) 353 if database == "" { 354 database = s.GetDatabase() 355 } 356 return s.Maintenance().ForDatabase(database).Send(op) 357 } 358 359 // GetRequestExecutor gets a request executor. 360 // database is optional 361 func (s *DocumentStore) GetRequestExecutor(database string) *RequestExecutor { 362 must(s.assertInitialized()) 363 if database == "" { 364 database = s.GetDatabase() 365 } 366 database = strings.ToLower(database) 367 368 s.mu.Lock() 369 executor, ok := s.requestsExecutors[database] 370 s.mu.Unlock() 371 372 if ok { 373 return executor 374 } 375 376 if !s.GetConventions().IsDisableTopologyUpdates() { 377 executor = RequestExecutorCreate(s.GetUrls(), database, s.Certificate, s.TrustStore, s.GetConventions()) 378 } else { 379 executor = RequestExecutorCreateForSingleNodeWithConfigurationUpdates(s.GetUrls()[0], database, s.Certificate, s.TrustStore, s.GetConventions()) 380 } 381 382 s.mu.Lock() 383 s.requestsExecutors[database] = executor 384 s.mu.Unlock() 385 386 return executor 387 } 388 389 // Initialize initializes document Store, 390 // Must be called before executing any operation. 391 func (s *DocumentStore) Initialize() error { 392 if s.initialized { 393 return nil 394 } 395 err := s.assertValidConfiguration() 396 if err != nil { 397 return err 398 } 399 400 conventions := s.conventions 401 if conventions.GetDocumentIDGenerator() == nil { 402 generator := NewMultiDatabaseHiLoIDGenerator(s, s.GetConventions()) 403 s.multiDbHiLo = generator 404 genID := func(dbName string, entity interface{}) (string, error) { 405 return generator.GenerateDocumentID(dbName, entity) 406 } 407 conventions.SetDocumentIDGenerator(genID) 408 } 409 s.initialized = true 410 return nil 411 } 412 413 func (s *DocumentStore) assertValidConfiguration() error { 414 if len(s.urls) == 0 { 415 return newIllegalArgumentError("Must provide urls to NewDocumentStore") 416 } 417 return nil 418 } 419 420 type RestoreCaching struct { 421 re *RequestExecutor 422 old *AggressiveCacheOptions 423 } 424 425 func (r *RestoreCaching) Close() error { 426 r.re.aggressiveCaching = r.old 427 return nil 428 } 429 430 func (s *DocumentStore) DisableAggressiveCaching(databaseName string) *RestoreCaching { 431 if databaseName == "" { 432 databaseName = s.GetDatabase() 433 } 434 435 re := s.GetRequestExecutor(databaseName) 436 old := re.aggressiveCaching 437 re.aggressiveCaching = nil 438 res := &RestoreCaching{ 439 re: re, 440 old: old, 441 } 442 return res 443 } 444 445 func (s *DocumentStore) Changes(database string) *DatabaseChanges { 446 must(s.assertInitialized()) 447 448 if database == "" { 449 database = s.GetDatabase() 450 } 451 452 s.mu.Lock() 453 changes, ok := s.databaseChanges[database] 454 s.mu.Unlock() 455 456 if !ok { 457 changes = s.createDatabaseChanges(database) 458 459 s.mu.Lock() 460 s.databaseChanges[database] = changes 461 s.mu.Unlock() 462 463 } 464 return changes 465 } 466 467 func (s *DocumentStore) createDatabaseChanges(database string) *DatabaseChanges { 468 panicIf(database == "", "database can't be empty string") 469 onDispose := func() { 470 s.mu.Lock() 471 delete(s.databaseChanges, database) 472 s.mu.Unlock() 473 } 474 re := s.GetRequestExecutor(database) 475 return newDatabaseChanges(re, database, onDispose) 476 } 477 478 func (s *DocumentStore) GetLastDatabaseChangesStateError(database string) error { 479 if database == "" { 480 database = s.GetDatabase() 481 } 482 483 s.mu.Lock() 484 databaseChanges, ok := s.databaseChanges[database] 485 s.mu.Unlock() 486 487 if !ok { 488 return nil 489 } 490 ch := databaseChanges 491 return ch.getLastConnectionStateError() 492 } 493 494 func (s *DocumentStore) AggressivelyCacheFor(cacheDuration time.Duration) (CancelFunc, error) { 495 return s.AggressivelyCacheForDatabase(cacheDuration, "") 496 } 497 498 func (s *DocumentStore) AggressivelyCacheForDatabase(cacheDuration time.Duration, database string) (CancelFunc, error) { 499 if database == "" { 500 database = s.GetDatabase() 501 } 502 if database == "" { 503 return nil, newIllegalArgumentError("must have database") 504 } 505 s.mu.Lock() 506 cachingUsed := s.aggressiveCachingUsed 507 s.mu.Unlock() 508 if !cachingUsed { 509 err := s.listenToChangesAndUpdateTheCache(database) 510 if err != nil { 511 return nil, err 512 } 513 } 514 515 // TODO: protect access to aggressiveCaching 516 opts := &AggressiveCacheOptions{ 517 Duration: cacheDuration, 518 } 519 re := s.GetRequestExecutor(database) 520 oldOpts := re.aggressiveCaching 521 re.aggressiveCaching = opts 522 523 restorer := func() { 524 re.aggressiveCaching = oldOpts 525 } 526 return restorer, nil 527 } 528 529 func (s *DocumentStore) listenToChangesAndUpdateTheCache(database string) error { 530 s.mu.Lock() 531 s.aggressiveCachingUsed = true 532 evict := s.aggressiveCacheChanges[database] 533 s.mu.Unlock() 534 535 if evict != nil { 536 return nil 537 } 538 539 evict, err := newEvictItemsFromCacheBasedOnChanges(s, database) 540 if err != nil { 541 return err 542 } 543 s.mu.Lock() 544 s.aggressiveCacheChanges[database] = evict 545 s.mu.Unlock() 546 return nil 547 } 548 549 func (s *DocumentStore) AddBeforeCloseListener(fn func(*DocumentStore)) int { 550 s.beforeClose = append(s.beforeClose, fn) 551 return len(s.beforeClose) - 1 552 } 553 554 func (s *DocumentStore) RemoveBeforeCloseListener(idx int) { 555 s.beforeClose[idx] = nil 556 } 557 558 func (s *DocumentStore) AddAfterCloseListener(fn func(*DocumentStore)) int { 559 s.afterClose = append(s.afterClose, fn) 560 return len(s.afterClose) - 1 561 } 562 563 func (s *DocumentStore) RemoveAfterCloseListener(idx int) { 564 s.afterClose[idx] = nil 565 } 566 567 func (s *DocumentStore) Maintenance() *MaintenanceOperationExecutor { 568 must(s.assertInitialized()) 569 570 if s.maintenanceOperationExecutor == nil { 571 s.maintenanceOperationExecutor = NewMaintenanceOperationExecutor(s, "") 572 } 573 574 return s.maintenanceOperationExecutor 575 } 576 577 func (s *DocumentStore) Operations() *OperationExecutor { 578 if s.operationExecutor == nil { 579 s.operationExecutor = NewOperationExecutor(s, "") 580 } 581 582 return s.operationExecutor 583 } 584 585 func (s *DocumentStore) BulkInsert(database string) *BulkInsertOperation { 586 if database == "" { 587 database = s.GetDatabase() 588 } 589 return NewBulkInsertOperation(database, s) 590 }