github.com/altipla-consulting/ravendb-go-client@v0.1.3/document_subscriptions.go (about) 1 package ravendb 2 3 import ( 4 "io" 5 "reflect" 6 "sync" 7 ) 8 9 // DocumentSubscriptions allows subscribing to changes in the store 10 type DocumentSubscriptions struct { 11 store *DocumentStore 12 subscriptions map[io.Closer]bool 13 mu sync.Mutex // protects subscriptions 14 } 15 16 func newDocumentSubscriptions(store *DocumentStore) *DocumentSubscriptions { 17 return &DocumentSubscriptions{ 18 store: store, 19 subscriptions: map[io.Closer]bool{}, 20 } 21 } 22 23 // Create creates a data subscription in a database. The subscription will expose all documents that match the specified subscription options for a given type. 24 func (s *DocumentSubscriptions) Create(options *SubscriptionCreationOptions, database string) (string, error) { 25 if options == nil { 26 return "", newIllegalArgumentError("Cannot create a subscription if options is nil") 27 } 28 29 if options.Query == "" { 30 return "", newIllegalArgumentError("Cannot create a subscription if Query is empty string") 31 } 32 33 if database == "" { 34 database = s.store.GetDatabase() 35 } 36 requestExecutor := s.store.GetRequestExecutor(database) 37 38 command := newCreateSubscriptionCommand(s.store.GetConventions(), options, "") 39 if err := requestExecutor.ExecuteCommand(command, nil); err != nil { 40 return "", err 41 } 42 43 return command.Result.Name, nil 44 } 45 46 // CreateForType creates a data subscription in a database. The subscription will 47 // expose all documents that match the specified subscription options for a given type. 48 func (s *DocumentSubscriptions) CreateForType(clazz reflect.Type, options *SubscriptionCreationOptions, database string) (string, error) { 49 if options == nil { 50 options = &SubscriptionCreationOptions{} 51 52 } 53 creationOptions := &SubscriptionCreationOptions{ 54 Name: options.Name, 55 ChangeVector: options.ChangeVector, 56 Query: options.Query, 57 } 58 59 opts := s.ensureCriteria(creationOptions, clazz, false) 60 return s.Create(opts, database) 61 } 62 63 // CreateForRevisions creates a data subscription in a database. The subscription will 64 // expose all documents that match the specified subscription options for a given type. 65 func (s *DocumentSubscriptions) CreateForRevisions(clazz reflect.Type, options *SubscriptionCreationOptions, database string) (string, error) { 66 if options == nil { 67 options = &SubscriptionCreationOptions{} 68 } 69 creationOptions := &SubscriptionCreationOptions{ 70 Name: options.Name, 71 ChangeVector: options.ChangeVector, 72 Query: options.Query, 73 } 74 75 opts := s.ensureCriteria(creationOptions, clazz, true) 76 return s.Create(opts, database) 77 } 78 79 func (s *DocumentSubscriptions) ensureCriteria(criteria *SubscriptionCreationOptions, clazz reflect.Type, revisions bool) *SubscriptionCreationOptions { 80 if criteria == nil { 81 criteria = &SubscriptionCreationOptions{} 82 } 83 84 collectionName := s.store.GetConventions().getCollectionName(clazz) 85 if criteria.Query == "" { 86 if revisions { 87 criteria.Query = "from " + collectionName + " (Revisions = true) as doc" 88 } else { 89 criteria.Query = "from " + collectionName + " as doc" 90 } 91 } 92 93 return criteria 94 } 95 96 // GetSubscriptionWorker opens a subscription and starts pulling documents since 97 // a last processed document for that subscription. 98 // The connection options determine client and server cooperation rules like 99 // document batch sizes or a timeout in a matter of which a client 100 // needs to acknowledge that batch has been processed. The acknowledgment 101 // is sent after all documents are processed by subscription's handlers. 102 func (s *DocumentSubscriptions) GetSubscriptionWorker(clazz reflect.Type, options *SubscriptionWorkerOptions, database string) (*SubscriptionWorker, error) { 103 if err := s.store.assertInitialized(); err != nil { 104 return nil, err 105 } 106 107 if options == nil { 108 return nil, newIllegalStateError("Cannot open a subscription if options are null") 109 } 110 111 subscription, err := NewSubscriptionWorker(clazz, options, false, s.store, database) 112 if err != nil { 113 return nil, err 114 } 115 fn := func(worker *SubscriptionWorker) { 116 s.mu.Lock() 117 delete(s.subscriptions, worker) 118 s.mu.Unlock() 119 } 120 subscription.onClosed = fn 121 s.mu.Lock() 122 s.subscriptions[subscription] = true 123 s.mu.Unlock() 124 125 return subscription, nil 126 } 127 128 // GetSubscriptionWorkerForRevisions opens a subscription and starts pulling documents 129 // since a last processed document for that subscription. 130 // The connection options determine client and server cooperation rules like document 131 // batch sizes or a timeout in a matter of which a client 132 // needs to acknowledge that batch has been processed. The acknowledgment is sent 133 // after all documents are processed by subscription's handlers. 134 func (s *DocumentSubscriptions) GetSubscriptionWorkerForRevisions(clazz reflect.Type, options *SubscriptionWorkerOptions, database string) (*SubscriptionWorker, error) { 135 subscription, err := NewSubscriptionWorker(clazz, options, true, s.store, database) 136 if err != nil { 137 return nil, err 138 } 139 140 fn := func(sender *SubscriptionWorker) { 141 s.mu.Lock() 142 delete(s.subscriptions, sender) 143 s.mu.Unlock() 144 } 145 146 subscription.onClosed = fn 147 s.mu.Lock() 148 s.subscriptions[subscription] = true 149 s.mu.Unlock() 150 151 return subscription, nil 152 } 153 154 // GetSubscriptions downloads a list of all existing subscriptions in a database. 155 func (s *DocumentSubscriptions) GetSubscriptions(start int, take int, database string) ([]*SubscriptionState, error) { 156 if database == "" { 157 database = s.store.GetDatabase() 158 } 159 requestExecutor := s.store.GetRequestExecutor(database) 160 161 command := newGetSubscriptionsCommand(start, take) 162 if err := requestExecutor.ExecuteCommand(command, nil); err != nil { 163 return nil, err 164 } 165 166 return command.Result, nil 167 } 168 169 // Delete deletes a subscription. 170 func (s *DocumentSubscriptions) Delete(name string, database string) error { 171 if database == "" { 172 database = s.store.GetDatabase() 173 } 174 requestExecutor := s.store.GetRequestExecutor(database) 175 176 command := newDeleteSubscriptionCommand(name) 177 return requestExecutor.ExecuteCommand(command, nil) 178 } 179 180 // GetSubscriptionState returns subscription definition and it's current state 181 func (s *DocumentSubscriptions) GetSubscriptionState(subscriptionName string, database string) (*SubscriptionState, error) { 182 if subscriptionName == "" { 183 return nil, newIllegalArgumentError("SubscriptionName cannot be null") 184 } 185 186 if database == "" { 187 database = s.store.GetDatabase() 188 } 189 requestExecutor := s.store.GetRequestExecutor(database) 190 191 command := newGetSubscriptionStateCommand(subscriptionName) 192 if err := requestExecutor.ExecuteCommand(command, nil); err != nil { 193 return nil, err 194 } 195 return command.Result, nil 196 } 197 198 // Close closes subscriptions 199 func (s *DocumentSubscriptions) Close() error { 200 if len(s.subscriptions) == 0 { 201 return nil 202 } 203 204 var err error 205 for subscription := range s.subscriptions { 206 err2 := subscription.Close() 207 if err2 != nil { 208 err = err2 209 } 210 } 211 return err 212 } 213 214 // DropConnection forces server to close current client subscription connection to the server 215 func (s *DocumentSubscriptions) DropConnection(name string, database string) error { 216 if database == "" { 217 database = s.store.GetDatabase() 218 } 219 requestExecutor := s.store.GetRequestExecutor(database) 220 221 command := newDropSubscriptionConnectionCommand(name) 222 return requestExecutor.ExecuteCommand(command, nil) 223 }