couchbase 3.0.0.alpha.3-universal-darwin-19 → 3.0.0.alpha.4-universal-darwin-19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/tests-6.0.3.yml +4 -1
  3. data/.github/workflows/tests-dev-preview.yml +4 -1
  4. data/.github/workflows/tests.yml +4 -1
  5. data/README.md +1 -1
  6. data/bin/check-cluster +31 -0
  7. data/bin/init-cluster +16 -4
  8. data/examples/analytics.rb +221 -0
  9. data/examples/managing_analytics_indexes.rb +72 -0
  10. data/examples/managing_view_indexes.rb +54 -0
  11. data/examples/search_with_consistency.rb +84 -0
  12. data/examples/view.rb +50 -0
  13. data/ext/.clang-tidy +1 -0
  14. data/ext/build_version.hxx.in +1 -1
  15. data/ext/couchbase/bucket.hxx +0 -1
  16. data/ext/couchbase/couchbase.cxx +1421 -55
  17. data/ext/couchbase/io/dns_client.hxx +215 -0
  18. data/ext/couchbase/io/dns_codec.hxx +207 -0
  19. data/ext/couchbase/io/dns_config.hxx +116 -0
  20. data/ext/couchbase/io/dns_message.hxx +558 -0
  21. data/ext/couchbase/io/http_session.hxx +16 -4
  22. data/ext/couchbase/io/mcbp_session.hxx +2 -1
  23. data/ext/couchbase/mutation_token.hxx +1 -1
  24. data/ext/couchbase/operations.hxx +19 -0
  25. data/ext/couchbase/operations/analytics_dataset_create.hxx +117 -0
  26. data/ext/couchbase/operations/analytics_dataset_drop.hxx +103 -0
  27. data/ext/couchbase/operations/analytics_dataset_get_all.hxx +107 -0
  28. data/ext/couchbase/operations/analytics_dataverse_create.hxx +104 -0
  29. data/ext/couchbase/operations/analytics_dataverse_drop.hxx +104 -0
  30. data/ext/couchbase/operations/analytics_get_pending_mutations.hxx +91 -0
  31. data/ext/couchbase/operations/analytics_index_create.hxx +128 -0
  32. data/ext/couchbase/operations/analytics_index_drop.hxx +110 -0
  33. data/ext/couchbase/operations/analytics_index_get_all.hxx +106 -0
  34. data/ext/couchbase/operations/analytics_link_connect.hxx +102 -0
  35. data/ext/couchbase/operations/analytics_link_disconnect.hxx +101 -0
  36. data/ext/couchbase/operations/design_document.hxx +59 -0
  37. data/ext/couchbase/operations/document_analytics.hxx +293 -0
  38. data/ext/couchbase/operations/document_query.hxx +2 -2
  39. data/ext/couchbase/operations/document_search.hxx +19 -1
  40. data/ext/couchbase/operations/document_view.hxx +227 -0
  41. data/ext/couchbase/operations/search_index.hxx +17 -0
  42. data/ext/couchbase/operations/search_index_control_ingest.hxx +3 -1
  43. data/ext/couchbase/operations/view_index_drop.hxx +67 -0
  44. data/ext/couchbase/operations/view_index_get.hxx +90 -0
  45. data/ext/couchbase/operations/view_index_get_all.hxx +125 -0
  46. data/ext/couchbase/operations/view_index_upsert.hxx +87 -0
  47. data/ext/couchbase/service_type.hxx +38 -1
  48. data/ext/couchbase/timeout_defaults.hxx +3 -1
  49. data/ext/couchbase/utils/connection_string.hxx +231 -0
  50. data/ext/couchbase/version.hxx +1 -1
  51. data/ext/test/main.cxx +3 -12
  52. data/lib/couchbase/analytics_options.rb +165 -0
  53. data/lib/couchbase/bucket.rb +49 -0
  54. data/lib/couchbase/cluster.rb +46 -207
  55. data/lib/couchbase/management/analytics_index_manager.rb +138 -24
  56. data/lib/couchbase/management/view_index_manager.rb +63 -10
  57. data/lib/couchbase/query_options.rb +219 -0
  58. data/lib/couchbase/search_options.rb +6 -6
  59. data/lib/couchbase/version.rb +1 -1
  60. data/lib/couchbase/view_options.rb +155 -0
  61. metadata +34 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5523baaeabe62572762a0af31a6636401970e9b8f760b071c855a9c59766e7ef
4
- data.tar.gz: 860e1fd99369e1e075fbc80629e051f4f1f41515b2ee084a64c5342a35fea7a2
3
+ metadata.gz: 2e8b1be7d23cb5c725e448db3ac54370e4dda4a4df9490ad1a78bdf66b0a2309
4
+ data.tar.gz: 22da4d1bf78ec51e65f6104b788e67769a1f48a3f6394fe821adde0115a376fb
5
5
  SHA512:
6
- metadata.gz: 7655d328ee6cd5b5b4d830deaf75053b0d5ccdd15ee77eafd188be81841c05f1433c560be08aa71a2b7ab58d59b5ff427fd1cee9ea095fb2874075b39c7b8023
7
- data.tar.gz: 85510dc74b15eadecf816aa8b59938dad14d9e1dc118d3f6b665161a581d1d4493faadd5e6cdece784a22bd63e3d036e07f734848de65a13a4857be620501ca7
6
+ metadata.gz: 741cd0b8ac16cd488af9d386c75483a9813b078e5d6f26a62c8b2569d5e3197df73bea32cd51a1048a0a6ed36dd78c5c32538b5f3ec4fb6d561bf95b4c71382c
7
+ data.tar.gz: 9ad2b0bf5c7178eb42620c079dabaec6e977bad7f40bdc6d554122c38856c88388b20d5bc8a0b55ccd50ad9c77da8fef54fe9e2fe03d20c95798dc19c657c64a
@@ -15,7 +15,7 @@ jobs:
15
15
  couchbase:
16
16
  image: couchbase:enterprise-6.0.3
17
17
  ports:
18
- - 8091-8094:8091-8094
18
+ - 8091-8095:8091-8095
19
19
  - 11210:11210
20
20
 
21
21
  steps:
@@ -43,6 +43,9 @@ jobs:
43
43
  - name: Display library version
44
44
  run: bundle exec ruby -I lib -r couchbase -e 'pp Couchbase::VERSION'
45
45
 
46
+ - name: Check couchbase
47
+ run: ./bin/check-cluster
48
+
46
49
  - name: Run tests
47
50
  run: bundle exec rake test
48
51
  env:
@@ -15,7 +15,7 @@ jobs:
15
15
  couchbase:
16
16
  image: couchbase:enterprise-6.5.1
17
17
  ports:
18
- - 8091-8094:8091-8094
18
+ - 8091-8095:8091-8095
19
19
  - 11210:11210
20
20
 
21
21
  steps:
@@ -45,6 +45,9 @@ jobs:
45
45
  - name: Display library version
46
46
  run: bundle exec ruby -I lib -r couchbase -e 'pp Couchbase::VERSION'
47
47
 
48
+ - name: Check couchbase
49
+ run: ./bin/check-cluster
50
+
48
51
  - name: Run tests
49
52
  run: bundle exec rake test
50
53
  env:
@@ -15,7 +15,7 @@ jobs:
15
15
  couchbase:
16
16
  image: couchbase
17
17
  ports:
18
- - 8091-8094:8091-8094
18
+ - 8091-8095:8091-8095
19
19
  - 11210:11210
20
20
 
21
21
  steps:
@@ -43,5 +43,8 @@ jobs:
43
43
  - name: Display library version
44
44
  run: bundle exec ruby -I lib -r couchbase -e 'pp Couchbase::VERSION'
45
45
 
46
+ - name: Check couchbase
47
+ run: ./bin/check-cluster
48
+
46
49
  - name: Run tests
47
50
  run: bundle exec rake test
data/README.md CHANGED
@@ -16,7 +16,7 @@ Please attach version information to ticket/post. To obtain this information use
16
16
  Add this line to your application's Gemfile:
17
17
 
18
18
  ```ruby
19
- gem "couchbase", "3.0.0.alpha.3"
19
+ gem "couchbase", "3.0.0.alpha.4"
20
20
  ```
21
21
 
22
22
  And then execute:
data/bin/check-cluster ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Copyright 2020 Couchbase, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ HOST=${HOST:-localhost}
18
+ USERNAME=${USERNAME:-Administrator}
19
+ PASSWORD=${PASSWOWRD:-password}
20
+
21
+ CREDS="${USERNAME}:${PASSWORD}"
22
+
23
+ sleep 1
24
+
25
+ set -ex
26
+
27
+ curl -sS -w "\n" -u${CREDS} http://${HOST}:8091/pools
28
+ curl -sS -w "\n" -u${CREDS} http://${HOST}:8091/pools/default
29
+ curl -sS -w "\n" -u${CREDS} http://${HOST}:8091/pools/default/buckets/default
30
+ curl -sS -w "\n" -u${CREDS} http://${HOST}:8091/pools/default/b/default
31
+
data/bin/init-cluster CHANGED
@@ -19,6 +19,8 @@ USERNAME=${USERNAME:-Administrator}
19
19
  PASSWORD=${PASSWOWRD:-password}
20
20
  QUOTA=${QUOTA:-256}
21
21
  DEVELOPER_PREVIEW=${DEVELOPER_PREVIEW:-no}
22
+ BEER_SAMPLE=${BEER_SAMPLE:-no}
23
+ TRAVEL_SAMPLE=${TRAVEL_SAMPLE:-no}
22
24
 
23
25
  CREDS="${USERNAME}:${PASSWORD}"
24
26
 
@@ -34,15 +36,15 @@ curl -sS -w "\n" -u ${CREDS} http://${HOST}:8091/nodes/self/controller/settings
34
36
  -d 'path=%2Fopt%2Fcouchbase%2Fvar%2Flib%2Fcouchbase%2Fdata' \
35
37
  -d 'index_path=%2Fopt%2Fcouchbase%2Fvar%2Flib%2Fcouchbase%2Fdata'
36
38
 
37
- # Setup Services
38
- curl -sS -w "\n" -u ${CREDS} http://${HOST}:8091/node/controller/setupServices \
39
- -d 'services=kv%2Cn1ql%2Cindex%2Cfts'
40
-
41
39
  # Setup Memory Quotas
42
40
  curl -sS -w "\n" -u ${CREDS} http://${HOST}:8091/pools/default \
43
41
  -d "memoryQuota=${QUOTA}" \
44
42
  -d 'indexMemoryQuota=256'
45
43
 
44
+ # Setup Services
45
+ curl -sS -w "\n" -u ${CREDS} http://${HOST}:8091/node/controller/setupServices \
46
+ -d 'services=kv%2Cn1ql%2Cindex%2Cfts%2Ccbas'
47
+
46
48
  # Setup Administrator username and password
47
49
  curl -sS -w "\n" -u ${CREDS} http://${HOST}:8091/settings/web \
48
50
  -d "password=${PASSWORD}" \
@@ -64,6 +66,16 @@ curl -sS -w "\n" -u ${CREDS} http://${HOST}:8091/pools/default/buckets \
64
66
  -d 'bucketType=membase' \
65
67
  -d 'name=default'
66
68
 
69
+ if [ "x${BEER_SAMPLE}" != "xno" ]; then
70
+ curl -sS -w "\n" -u ${CREDS} http://${HOST}:8091/sampleBuckets/install \
71
+ -d '["beer-sample"]'
72
+ fi
73
+
74
+ if [ "x${TRAVEL_SAMPLE}" != "xno" ]; then
75
+ curl -sS -w "\n" -u ${CREDS} http://${HOST}:8091/sampleBuckets/install \
76
+ -d '["travel-sample"]'
77
+ fi
78
+
67
79
  if [ "x${DEVELOPER_PREVIEW}" != "xno" ]; then
68
80
  curl -sS -w "\n" -u ${CREDS} http://${HOST}:8091/settings/developerPreview \
69
81
  -d "enabled=true"
@@ -0,0 +1,221 @@
1
+ require 'couchbase'
2
+
3
+ include Couchbase
4
+
5
+ options = Cluster::ClusterOptions.new
6
+ options.authenticate("Administrator", "password")
7
+ cluster = Cluster.connect("couchbase://localhost", options)
8
+
9
+
10
+ bucket_name = "tiny_social"
11
+ dataverse_name = "TinySocial"
12
+
13
+ # Prepare dataset
14
+ begin
15
+ cluster.buckets.drop_bucket(bucket_name)
16
+ rescue Error::BucketNotFound
17
+ # ignore
18
+ end
19
+
20
+ settings = Management::BucketSettings.new
21
+ settings.name = bucket_name
22
+ settings.bucket_type = :couchbase
23
+ settings.ram_quota_mb = 100
24
+ cluster.buckets.create_bucket(settings)
25
+ loop do
26
+ sleep 1
27
+ break if cluster.buckets.get_all_buckets.find { |b| b.name == bucket_name }.healthy?
28
+ end
29
+
30
+ collection = cluster.bucket(bucket_name).default_collection
31
+
32
+ # Documents for GleambookUsers dataset
33
+ [
34
+ {
35
+ "id" => 1,
36
+ "alias" => "Margarita",
37
+ "name" => "MargaritaStoddard",
38
+ "nickname" => "Mags",
39
+ "userSince" => "2012-08-20T10:10:00",
40
+ "friendIds" => [2, 3, 6, 10],
41
+ "employment" => [
42
+ {
43
+ "organizationName" => "Codetechno",
44
+ "start-date" => "2006-08-06"
45
+ },
46
+ {
47
+ "organizationName" => "geomedia",
48
+ "start-date" => "2010-06-17",
49
+ "end-date" => "2010-01-26"
50
+ }
51
+ ],
52
+ "gender" => "F"
53
+ },
54
+ {
55
+ "id" => 2,
56
+ "alias" => "Isbel",
57
+ "name" => "IsbelDull",
58
+ "nickname" => "Izzy",
59
+ "userSince" => "2011-01-22T10:10:00",
60
+ "friendIds" => [1, 4],
61
+ "employment" => [
62
+ {
63
+ "organizationName" => "Hexviafind",
64
+ "startDate" => "2010-04-27"
65
+ }
66
+ ]
67
+ },
68
+ {
69
+ "id" => 3,
70
+ "alias" => "Emory",
71
+ "name" => "EmoryUnk",
72
+ "userSince" => "2012-07-10T10:10:00",
73
+ "friendIds" => [1, 5, 8, 9],
74
+ "employment" => [
75
+ {
76
+ "organizationName" => "geomedia",
77
+ "startDate" => "2010-06-17",
78
+ "endDate" => "2010-01-26"
79
+ }
80
+ ]
81
+ }
82
+ ].each do |document|
83
+ collection.upsert("user:#{document["id"]}",
84
+ document.merge({"type" => "user"}))
85
+ end
86
+
87
+ # Documents for GleambookMessages dataset
88
+ [
89
+ {
90
+ "messageId" => 2,
91
+ "authorId" => 1,
92
+ "inResponseTo" => 4,
93
+ "senderLocation" => [41.66, 80.87],
94
+ "message" => " dislike x-phone its touch-screen is horrible"
95
+ },
96
+ {
97
+ "messageId" => 3,
98
+ "authorId" => 2,
99
+ "inResponseTo" => 4,
100
+ "senderLocation" => [48.09, 81.01],
101
+ "message" => " like product-y the plan is amazing"
102
+ },
103
+ {
104
+ "messageId" => 4,
105
+ "authorId" => 1,
106
+ "inResponseTo" => 2,
107
+ "senderLocation" => [37.73, 97.04],
108
+ "message" => " can't stand acast the network is horrible:("
109
+ },
110
+ {
111
+ "messageId" => 6,
112
+ "authorId" => 2,
113
+ "inResponseTo" => 1,
114
+ "senderLocation" => [31.5, 75.56],
115
+ "message" => " like product-z its platform is mind-blowing"
116
+ },
117
+ {
118
+ "messageId" => 8,
119
+ "authorId" => 1,
120
+ "inResponseTo" => 11,
121
+ "senderLocation" => [40.33, 80.87],
122
+ "message" => " like ccast the 3G is awesome:)"
123
+ },
124
+ {
125
+ "messageId" => 10,
126
+ "authorId" => 1,
127
+ "inResponseTo" => 12,
128
+ "senderLocation" => [42.5, 70.01],
129
+ "message" => " can't stand product-w the touch-screen is terrible"
130
+ },
131
+ {
132
+ "messageId" => 11,
133
+ "authorId" => 1,
134
+ "inResponseTo" => 1,
135
+ "senderLocation" => [38.97, 77.49],
136
+ "message" => " can't stand acast its plan is terrible"
137
+ }
138
+ ].each do |document|
139
+ collection.upsert("message:#{document["messageId"]}",
140
+ document.merge({"type" => "message"}))
141
+ end
142
+
143
+ if cluster.analytics_indexes.get_all_datasets.any? {|ds| ds.dataverse_name == dataverse_name}
144
+ # there are datasets on our dataverse, drop everything and re-create
145
+ options = Management::AnalyticsIndexManager::DisconnectLinkOptions.new
146
+ options.dataverse_name = dataverse_name
147
+ cluster.analytics_indexes.disconnect_link(options)
148
+ cluster.analytics_indexes.drop_dataverse(dataverse_name)
149
+ end
150
+
151
+ cluster.analytics_indexes.create_dataverse(dataverse_name)
152
+
153
+ options = Management::AnalyticsIndexManager::CreateDatasetOptions.new
154
+ options.dataverse_name = dataverse_name
155
+
156
+ options.condition = '`type` = "user"'
157
+ cluster.analytics_indexes.create_dataset("GleambookUsers", bucket_name, options)
158
+
159
+ options.condition = '`type` = "message"'
160
+ cluster.analytics_indexes.create_dataset("GleambookMessages", bucket_name, options)
161
+
162
+ options = Management::AnalyticsIndexManager::ConnectLinkOptions.new
163
+ options.dataverse_name = dataverse_name
164
+ cluster.analytics_indexes.connect_link(options)
165
+
166
+ sleep(1)
167
+
168
+ puts "---- inner join"
169
+ res = cluster.analytics_query("SELECT * FROM #{dataverse_name}.GleambookUsers u, #{dataverse_name}.GleambookMessages m WHERE m.authorId = u.id")
170
+ res.rows.each do |row|
171
+ puts "#{row["u"]["name"]}: #{row["m"]["message"].inspect}"
172
+ end
173
+
174
+ # The query language supports SQL's notion of left outer join.
175
+ puts "---- left outer join"
176
+ res = cluster.analytics_query("
177
+ USE #{dataverse_name};
178
+ SELECT u.name AS uname, m.message AS message
179
+ FROM GleambookUsers u
180
+ LEFT OUTER JOIN GleambookMessages m ON m.authorId = u.id
181
+ ")
182
+ res.rows.each do |row|
183
+ puts "#{row["uname"]}: #{row["message"].inspect}"
184
+ end
185
+
186
+ # Named parameters
187
+ puts "---- named parameters"
188
+ options = Cluster::AnalyticsOptions.new
189
+ options.named_parameters({user_id: 2})
190
+ res = cluster.analytics_query("
191
+ USE #{dataverse_name};
192
+
193
+ SELECT u.name AS uname
194
+ FROM GleambookUsers u
195
+ WHERE u.id = $user_id
196
+
197
+ UNION ALL
198
+
199
+ SELECT VALUE m.message
200
+ FROM GleambookMessages m
201
+ WHERE authorId = $user_id
202
+ ", options)
203
+ res.rows.each do |row|
204
+ p row
205
+ end
206
+
207
+ # Positional parameters
208
+ puts "---- positional parameters"
209
+ options = Cluster::AnalyticsOptions.new
210
+ options.positional_parameters([2])
211
+ res = cluster.analytics_query("
212
+ USE #{dataverse_name};
213
+ SELECT VALUE user
214
+ FROM GleambookUsers AS user
215
+ WHERE len(user.friendIds) > $1
216
+ ", options)
217
+ res.rows.each do |row|
218
+ puts "#{row["name"]}: #{row["friendIds"].size}"
219
+ end
220
+
221
+ # More query examples at https://docs.couchbase.com/server/current/analytics/3_query.html
@@ -0,0 +1,72 @@
1
+ require 'couchbase'
2
+ include Couchbase
3
+
4
+ def measure(msg)
5
+ start = Time.now
6
+ yield
7
+ printf "%s in %.2f seconds\n", msg, Time.now - start
8
+ end
9
+
10
+ options = Cluster::ClusterOptions.new
11
+ options.authenticate("Administrator", "password")
12
+ cluster = Cluster.connect("couchbase://localhost", options)
13
+
14
+ manager = cluster.analytics_indexes
15
+
16
+ options = Management::AnalyticsIndexManager::DropDatasetOptions.new
17
+ options.ignore_if_does_not_exist = true
18
+ options.dataverse_name = "beer-data"
19
+ manager.drop_dataset("beers", options)
20
+ manager.drop_dataset("breweries", options)
21
+
22
+ options = Management::AnalyticsIndexManager::DropDataverseOptions.new
23
+ options.ignore_if_does_not_exist = true
24
+ manager.drop_dataverse("beer-data", options)
25
+
26
+ # Creates a dataverse with the name beer-data to be used to manage other metadata entities.
27
+ manager.create_dataverse("beer-data")
28
+
29
+ # Creates 2 datasets beers and breweries on the beer-sample bucket and filters the content for each dataset by the value
30
+ # of the type field of the record.
31
+ options = Management::AnalyticsIndexManager::CreateDatasetOptions.new
32
+ options.dataverse_name = "beer-data"
33
+ options.condition = '`type` = "beer"'
34
+ manager.create_dataset("beers", "beer-sample", options)
35
+
36
+ options = Management::AnalyticsIndexManager::CreateDatasetOptions.new
37
+ options.dataverse_name = "beer-data"
38
+ options.condition = '`type` = "brewery"'
39
+ manager.create_dataset("breweries", "beer-sample", options)
40
+
41
+ # Creates indexes on the identified fields for the specified types.
42
+ options = Management::AnalyticsIndexManager::CreateIndexOptions.new
43
+ options.dataverse_name = "beer-data"
44
+ manager.create_index("beers_name_idx", "beers", {"name" => "string"}, options)
45
+ manager.create_index("breweries_name_idx", "beers", {"name" => "string"}, options)
46
+ manager.create_index("breweries_loc_idx", "beers", {"geo.lon" => "double", "geo.lat" => "double"}, options)
47
+
48
+ puts "---- Indexes currently defined on the cluster:"
49
+ manager.get_all_indexes.each_with_index do |index, i|
50
+ puts "#{i}. #{index.dataverse_name}.#{index.dataset_name}.#{index.name} #{"(primary)" if index.primary?}"
51
+ end
52
+
53
+ # Drops one of the indexes
54
+ options = Management::AnalyticsIndexManager::DropIndexOptions.new
55
+ options.dataverse_name = "beer-data"
56
+ manager.drop_index("breweries_name_idx", "beers", options)
57
+
58
+ puts "---- Datasets currently defined on the cluster:"
59
+ manager.get_all_datasets.each_with_index do |dataset, i|
60
+ puts "#{i}. #{dataset.dataverse_name}.#{dataset.name} (link: #{dataset.link_name}, bucket: #{dataset.bucket_name})"
61
+ end
62
+
63
+ # Connects all datasets to their Data Service buckets and starts shadowing
64
+ options = Management::AnalyticsIndexManager::ConnectLinkOptions.new
65
+ options.dataverse_name = "beer-data"
66
+ manager.connect_link(options)
67
+
68
+ puts "---- Pending mutations: #{manager.get_pending_mutations.inspect}"
69
+
70
+ options = Management::AnalyticsIndexManager::DisconnectLinkOptions.new
71
+ options.dataverse_name = "beer-data"
72
+ manager.disconnect_link(options)