mongoid 7.2.5 → 7.2.6

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 (38) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +3 -3
  3. data/lib/config/locales/en.yml +13 -0
  4. data/lib/mongoid/association/relatable.rb +2 -0
  5. data/lib/mongoid/config/environment.rb +9 -1
  6. data/lib/mongoid/contextual/atomic.rb +7 -2
  7. data/lib/mongoid/contextual/none.rb +3 -0
  8. data/lib/mongoid/criteria/queryable/selectable.rb +2 -2
  9. data/lib/mongoid/criteria/queryable/storable.rb +4 -4
  10. data/lib/mongoid/document.rb +3 -2
  11. data/lib/mongoid/errors/empty_config_file.rb +26 -0
  12. data/lib/mongoid/errors/invalid_config_file.rb +26 -0
  13. data/lib/mongoid/errors.rb +2 -0
  14. data/lib/mongoid/persistence_context.rb +3 -1
  15. data/lib/mongoid/query_cache.rb +11 -1
  16. data/lib/mongoid/tasks/database.rb +1 -1
  17. data/lib/mongoid/version.rb +1 -1
  18. data/spec/integration/contextual/empty_spec.rb +142 -0
  19. data/spec/integration/stringified_symbol_field_spec.rb +2 -2
  20. data/spec/mongoid/association/referenced/belongs_to_query_spec.rb +20 -0
  21. data/spec/mongoid/association/referenced/has_many_models.rb +17 -0
  22. data/spec/mongoid/clients/factory_spec.rb +9 -3
  23. data/spec/mongoid/clients/options_spec.rb +11 -5
  24. data/spec/mongoid/config/environment_spec.rb +86 -8
  25. data/spec/mongoid/contextual/atomic_spec.rb +64 -25
  26. data/spec/mongoid/contextual/geo_near_spec.rb +1 -1
  27. data/spec/mongoid/document_spec.rb +21 -1
  28. data/spec/mongoid/errors/invalid_config_file_spec.rb +32 -0
  29. data/spec/mongoid/persistable/updatable_spec.rb +2 -0
  30. data/spec/mongoid/query_cache_spec.rb +24 -0
  31. data/spec/shared/lib/mrss/constraints.rb +21 -4
  32. data/spec/shared/lib/mrss/event_subscriber.rb +200 -0
  33. data/spec/shared/lib/mrss/server_version_registry.rb +17 -12
  34. data/spec/shared/share/Dockerfile.erb +5 -4
  35. data/spec/shared/shlib/server.sh +71 -21
  36. data.tar.gz.sig +0 -0
  37. metadata +544 -536
  38. metadata.gz.sig +0 -0
@@ -458,6 +458,7 @@ describe Mongoid::Persistable::Updatable do
458
458
  describe "##{method}" do
459
459
 
460
460
  context "when saving with a hash field with invalid keys" do
461
+ max_server_version '4.9'
461
462
 
462
463
  let(:person) do
463
464
  Person.create
@@ -494,6 +495,7 @@ describe Mongoid::Persistable::Updatable do
494
495
  end
495
496
 
496
497
  context "when the document has been destroyed" do
498
+ max_server_version '4.9'
497
499
 
498
500
  let(:person) do
499
501
  Person.create
@@ -24,10 +24,27 @@ describe Mongoid::QueryCache do
24
24
  SessionRegistry.instance.verify_sessions_ended!
25
25
  end
26
26
 
27
+ let(:reset_legacy_qc_warning) do
28
+ begin
29
+ Mongoid::QueryCache.remove_instance_variable('@legacy_query_cache_warned')
30
+ rescue NameError
31
+ # raised if the instance variable wasn't set
32
+ end
33
+ end
34
+
27
35
  describe '#cache' do
28
36
  context 'with driver query cache' do
29
37
  min_driver_version '2.14'
30
38
 
39
+ it 'does not log a deprecation warning' do
40
+ reset_legacy_qc_warning
41
+
42
+ expect_any_instance_of(Logger).to_not receive(:warn).with(
43
+ described_class::LEGACY_WARNING
44
+ )
45
+ described_class.cache { }
46
+ end
47
+
31
48
  context 'when query cache is not enabled' do
32
49
  before do
33
50
  Mongoid::QueryCache.enabled = false
@@ -180,6 +197,13 @@ describe Mongoid::QueryCache do
180
197
  context 'with mongoid query cache' do
181
198
  max_driver_version '2.13'
182
199
 
200
+ it 'logs a deprecation warning' do
201
+ reset_legacy_qc_warning
202
+
203
+ expect_any_instance_of(Logger).to receive(:warn).with(described_class::LEGACY_WARNING)
204
+ described_class.cache { }
205
+ end
206
+
183
207
  context 'when query cache is not enabled' do
184
208
  before do
185
209
  Mongoid::QueryCache.enabled = false
@@ -257,22 +257,39 @@ module Mrss
257
257
  end
258
258
  end
259
259
 
260
- def require_multi_shard
260
+ def require_multi_mongos
261
261
  before(:all) do
262
262
  if ClusterConfig.instance.topology == :sharded && SpecConfig.instance.addresses.length == 1
263
- skip 'Test requires a minimum of two shards if run in sharded topology'
263
+ skip 'Test requires a minimum of two mongoses if run in sharded topology'
264
+ end
265
+
266
+ if ClusterConfig.instance.topology == :load_balanced && SpecConfig.instance.single_mongos?
267
+ skip 'Test requires a minimum of two mongoses if run in load-balanced topology'
264
268
  end
265
269
  end
266
270
  end
267
271
 
268
- def require_no_multi_shard
272
+ # In sharded topology operations are distributed to the mongoses.
273
+ # When we set fail points, the fail point may be set on one mongos and
274
+ # operation may be executed on another mongos, causing failures.
275
+ # Tests that are not setting targeted fail points should utilize this
276
+ # method to restrict themselves to single mongos.
277
+ #
278
+ # In load-balanced topology, the same problem can happen when there is
279
+ # more than one mongos behind the load balancer.
280
+ def require_no_multi_mongos
269
281
  before(:all) do
270
282
  if ClusterConfig.instance.topology == :sharded && SpecConfig.instance.addresses.length > 1
271
- skip 'Test requires a single shard if run in sharded topology'
283
+ skip 'Test requires a single mongos if run in sharded topology'
284
+ end
285
+ if ClusterConfig.instance.topology == :load_balanced && !SpecConfig.instance.single_mongos?
286
+ skip 'Test requires a single mongos, as indicated by SINGLE_MONGOS=1 environment variable, if run in load-balanced topology'
272
287
  end
273
288
  end
274
289
  end
275
290
 
291
+ alias :require_no_multi_shard :require_no_multi_mongos
292
+
276
293
  def require_wired_tiger
277
294
  before(:all) do
278
295
  # Storage detection fails for serverless instances. However, it is safe to
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mrss
4
+ # Test event subscriber.
5
+ class EventSubscriber
6
+
7
+ # The mappings of event names to types.
8
+ MAPPINGS = {
9
+ 'topology_opening_event' => Mongo::Monitoring::Event::TopologyOpening,
10
+ 'topology_description_changed_event' => Mongo::Monitoring::Event::TopologyChanged,
11
+ 'topology_closed_event' => Mongo::Monitoring::Event::TopologyClosed,
12
+ 'server_opening_event' => Mongo::Monitoring::Event::ServerOpening,
13
+ 'server_description_changed_event' => Mongo::Monitoring::Event::ServerDescriptionChanged,
14
+ 'server_closed_event' => Mongo::Monitoring::Event::ServerClosed
15
+ }.freeze
16
+
17
+ attr_reader :all_events
18
+
19
+ attr_reader :started_events
20
+
21
+ attr_reader :succeeded_events
22
+
23
+ attr_reader :failed_events
24
+
25
+ attr_reader :published_events
26
+
27
+ # @param [ String ] name Optional name for the event subscriber.
28
+ def initialize(name: nil)
29
+ @mutex = Mutex.new
30
+ clear_events!
31
+ @name = name
32
+ end
33
+
34
+ def to_s
35
+ %Q`#<EventSubscriber:#{@name ? "\"#{@name}\"" : '%x' % object_id} \
36
+ started=#{started_events.length} \
37
+ succeeded=#{succeeded_events.length} \
38
+ failed=#{failed_events.length} \
39
+ published=#{published_events.length}>`
40
+ end
41
+
42
+ alias :inspect :to_s
43
+
44
+ # Event retrieval
45
+
46
+ def select_started_events(cls)
47
+ started_events.select do |event|
48
+ event.is_a?(cls)
49
+ end
50
+ end
51
+
52
+ def select_succeeded_events(cls)
53
+ succeeded_events.select do |event|
54
+ event.is_a?(cls)
55
+ end
56
+ end
57
+
58
+ def select_completed_events(*classes)
59
+ (succeeded_events + failed_events).select do |event|
60
+ classes.any? { |c| c === event }
61
+ end
62
+ end
63
+
64
+ def select_published_events(cls)
65
+ published_events.select do |event|
66
+ event.is_a?(cls)
67
+ end
68
+ end
69
+
70
+ # Filters command started events for the specified command name.
71
+ def command_started_events(command_name)
72
+ started_events.select do |event|
73
+ event.command[command_name]
74
+ end
75
+ end
76
+
77
+ def non_auth_command_started_events
78
+ started_events.reject do |event|
79
+ %w(authenticate getnonce saslSstart saslContinue).any? do |cmd|
80
+ event.command[cmd]
81
+ end
82
+ end
83
+ end
84
+
85
+ # Locates command stated events for the specified command name,
86
+ # asserts that there is exactly one such event, and returns it.
87
+ def single_command_started_event(command_name, include_auth: false)
88
+ events = if include_auth
89
+ started_events
90
+ else
91
+ non_auth_command_started_events
92
+ end
93
+ events.select! do |event|
94
+ event.command[command_name]
95
+ end
96
+ if events.length != 1
97
+ raise "Expected a single #{command_name} event but we have #{events.length}"
98
+ end
99
+ events.first
100
+ end
101
+
102
+
103
+ # Get the first succeeded event published for the name, and then delete it.
104
+ #
105
+ # @param [ String ] name The event name.
106
+ #
107
+ # @return [ Event ] The matching event.
108
+ def first_event(name)
109
+ cls = MAPPINGS[name]
110
+ if cls.nil?
111
+ raise ArgumentError, "Bogus event name #{name}"
112
+ end
113
+ matching = succeeded_events.find do |event|
114
+ cls === event
115
+ end
116
+ succeeded_events.delete(matching)
117
+ matching
118
+ end
119
+
120
+ # Event recording
121
+
122
+ # Cache the started event.
123
+ #
124
+ # @param [ Event ] event The event.
125
+ def started(event)
126
+ @mutex.synchronize do
127
+ started_events << event
128
+ all_events << event
129
+ end
130
+ end
131
+
132
+ # Cache the succeeded event.
133
+ #
134
+ # @param [ Event ] event The event.
135
+ def succeeded(event)
136
+ @mutex.synchronize do
137
+ succeeded_events << event
138
+ all_events << event
139
+ end
140
+ end
141
+
142
+ # Cache the failed event.
143
+ #
144
+ # @param [ Event ] event The event.
145
+ def failed(event)
146
+ @mutex.synchronize do
147
+ failed_events << event
148
+ all_events << event
149
+ end
150
+ end
151
+
152
+ def published(event)
153
+ @mutex.synchronize do
154
+ published_events << event
155
+ all_events << event
156
+ end
157
+ end
158
+
159
+ # Clear all cached events.
160
+ def clear_events!
161
+ @all_events = []
162
+ @started_events = []
163
+ @succeeded_events = []
164
+ @failed_events = []
165
+ @published_events = []
166
+ self
167
+ end
168
+ end
169
+ # Only handles succeeded events correctly.
170
+ class PhasedEventSubscriber < EventSubscriber
171
+ def initialize
172
+ super
173
+ @phase_events = {}
174
+ end
175
+
176
+ def phase_finished(phase_index)
177
+ @phase_events[phase_index] = succeeded_events
178
+ @succeeded_events = []
179
+ end
180
+
181
+ def phase_events(phase_index)
182
+ @phase_events[phase_index]
183
+ end
184
+
185
+ def event_count
186
+ @phase_events.inject(0) do |sum, event|
187
+ sum + event.length
188
+ end
189
+ end
190
+ end
191
+
192
+ class VerboseEventSubscriber < EventSubscriber
193
+ %w(started succeeded failed published).each do |meth|
194
+ define_method(meth) do |event|
195
+ puts event.summary
196
+ super(event)
197
+ end
198
+ end
199
+ end
200
+ end
@@ -48,19 +48,24 @@ module Mrss
48
48
  url = dl['archive']['url']
49
49
  end
50
50
  rescue MissingDownloadUrl
51
- if %w(4.7 4.7.0).include?(desired_version)
52
- # 4.7.0 has no advertised downloads but it is downloadable and
53
- # we do need it. Dirty hack below.
54
- registry = self.class.new('4.4.3', arch)
55
- registry.download_url.sub('4.4.3', '4.7.0').tap do |url|
56
- # Sanity check - ensure the URL we hacked up is a valid one
57
- io = uri_open(url)
58
- begin
59
- io.read(1)
60
- ensure
61
- io.close
62
- end
51
+ if %w(2.6 3.0).include?(desired_version) && arch == 'ubuntu1604'
52
+ # 2.6 and 3.0 are only available for ubuntu1204 and ubuntu1404.
53
+ # Those ubuntus have ancient Pythons that don't work due to not
54
+ # implementing recent TLS protocols.
55
+ # Because of this we test on ubuntu1604 which has a newer Python.
56
+ # But we still need to retrieve ubuntu1404-targeting builds.
57
+ url = self.class.new('3.2', arch).download_url
58
+ unless url.include?('3.2.')
59
+ raise 'URL not in expected format'
63
60
  end
61
+ url = case desired_version
62
+ when '2.6'
63
+ url.sub(/\b3\.2\.\d+/, '2.6.12')
64
+ when '3.0'
65
+ url.sub(/\b3\.2\.\d+/, '3.0.15')
66
+ else
67
+ raise NotImplementedError
68
+ end.sub('ubuntu1604', 'ubuntu1404')
64
69
  else
65
70
  raise
66
71
  end
@@ -55,7 +55,7 @@ FROM <%= base_image %>
55
55
  # Ruby runtime dependencies: libyaml-0-2
56
56
  # Compiling ruby libraries: gcc make
57
57
  # Compiling pyhton packages: python2.7-dev
58
- # JRuby: openjdk-8-jre-headless
58
+ # JRuby: openjdk-8-jdk-headless
59
59
  # Server dependencies: libsnmp30 libcurl3/libcurl4
60
60
  # Determining OS we are running on: lsb-release
61
61
  # Load balancer testing: haproxy
@@ -68,6 +68,7 @@ FROM <%= base_image %>
68
68
  # nokogiri: zlib1g-dev
69
69
  # Mongoid testing: tzdata shared-mime-info
70
70
  # Mongoid application testing: nodejs (8.x or newer)
71
+ # Test suite: procps for ps (to kill JRubies)
71
72
  #
72
73
  # We currently use Python 2-compatible version of mtools, which
73
74
  # is installable via pip (which uses Python 2). All of the MongoDB
@@ -75,7 +76,7 @@ FROM <%= base_image %>
75
76
  # therefore install python-pip in all configurations here.
76
77
 
77
78
  <% packages = %w(
78
- lsb-release bzip2 curl zsh
79
+ procps lsb-release bzip2 curl zsh
79
80
  git make gcc libyaml-0-2 libgmp-dev zlib1g-dev libsnappy-dev
80
81
  krb5-user krb5-kdc krb5-admin-server libsasl2-dev libsasl2-modules-gssapi-mit
81
82
  haproxy
@@ -103,11 +104,11 @@ FROM <%= base_image %>
103
104
  <% end %>
104
105
 
105
106
  <% if distro =~ /debian10/ %>
106
- <% packages << 'openjdk-11-jre-headless' %>
107
+ <% packages << 'openjdk-11-jdk-headless' %>
107
108
  <% elsif distro =~ /ubuntu1404/ %>
108
109
  # Ubuntu 14.04 only has openjdk 7, this is too old to be useful
109
110
  <% else %>
110
- <% packages << 'openjdk-8-jre-headless' %>
111
+ <% packages << 'openjdk-8-jdk-headless' %>
111
112
  <% end %>
112
113
 
113
114
  # ubuntu1404, ubuntu1604: libcurl3
@@ -10,21 +10,21 @@ set_fcv() {
10
10
 
11
11
  add_uri_option() {
12
12
  opt=$1
13
-
13
+
14
14
  if ! echo $MONGODB_URI |sed -e s,//,, |grep -q /; then
15
15
  MONGODB_URI="$MONGODB_URI/"
16
16
  fi
17
-
17
+
18
18
  if ! echo $MONGODB_URI |grep -q '?'; then
19
19
  MONGODB_URI="$MONGODB_URI?"
20
20
  fi
21
-
21
+
22
22
  MONGODB_URI="$MONGODB_URI&$opt"
23
23
  }
24
24
 
25
25
  prepare_server() {
26
26
  arch=$1
27
-
27
+
28
28
  if test -n "$USE_OPT_MONGODB"; then
29
29
  export BINDIR=/opt/mongodb/bin
30
30
  export PATH=$BINDIR:$PATH
@@ -39,7 +39,7 @@ prepare_server() {
39
39
  else
40
40
  download_version="$MONGODB_VERSION"
41
41
  fi
42
-
42
+
43
43
  url=`$(dirname $0)/get-mongodb-download-url $download_version $arch`
44
44
 
45
45
  prepare_server_from_url $url
@@ -80,7 +80,7 @@ install_mlaunch_pip() {
80
80
  # mlaunch is preinstalled in the docker image, do not install it here
81
81
  return
82
82
  fi
83
-
83
+
84
84
  python -V || true
85
85
  python3 -V || true
86
86
  pythonpath="$MONGO_ORCHESTRATION_HOME"/python
@@ -96,20 +96,20 @@ install_mlaunch_git() {
96
96
  python3 -V || true
97
97
  which pip || true
98
98
  which pip3 || true
99
-
99
+
100
100
  if false; then
101
101
  if ! virtualenv --version; then
102
102
  python3 `which pip3` install --user virtualenv
103
103
  export PATH=$HOME/.local/bin:$PATH
104
104
  virtualenv --version
105
105
  fi
106
-
106
+
107
107
  venvpath="$MONGO_ORCHESTRATION_HOME"/venv
108
108
  virtualenv -p python3 $venvpath
109
109
  . $venvpath/bin/activate
110
-
110
+
111
111
  pip3 install psutil pymongo
112
-
112
+
113
113
  git clone $repo mlaunch
114
114
  cd mlaunch
115
115
  git checkout origin/$branch
@@ -118,13 +118,13 @@ install_mlaunch_git() {
118
118
  else
119
119
  pip install --user 'virtualenv==13'
120
120
  export PATH=$HOME/.local/bin:$PATH
121
-
121
+
122
122
  venvpath="$MONGO_ORCHESTRATION_HOME"/venv
123
123
  virtualenv $venvpath
124
124
  . $venvpath/bin/activate
125
-
125
+
126
126
  pip install psutil pymongo
127
-
127
+
128
128
  git clone $repo mlaunch
129
129
  (cd mlaunch &&
130
130
  git checkout origin/$branch &&
@@ -133,26 +133,32 @@ install_mlaunch_git() {
133
133
  fi
134
134
  }
135
135
 
136
+ # This function sets followong global variables:
137
+ # server_cert_path
138
+ # server_ca_path
139
+ # server_client_cert_path
140
+ #
141
+ # These variables are used later to connect to processes via mongo client.
136
142
  calculate_server_args() {
137
143
  local mongo_version=`echo $MONGODB_VERSION |tr -d .`
138
-
144
+
139
145
  if test -z "$mongo_version"; then
140
146
  echo "$MONGODB_VERSION must be set and not contain only dots" 1>&2
141
147
  exit 3
142
148
  fi
143
-
149
+
144
150
  if test $mongo_version = latest; then
145
151
  mongo_version=49
146
152
  fi
147
153
 
148
154
  local args="--setParameter enableTestCommands=1"
149
-
155
+
150
156
  if test $mongo_version -ge 50; then
151
157
  args="$args --setParameter acceptApiVersion2=1"
152
158
  elif test $mongo_version -ge 47; then
153
159
  args="$args --setParameter acceptAPIVersion2=1"
154
160
  fi
155
-
161
+
156
162
  # diagnosticDataCollectionEnabled is a mongod-only parameter on server 3.2,
157
163
  # and mlaunch does not support specifying mongod-only parameters:
158
164
  # https://github.com/rueckstiess/mtools/issues/696
@@ -181,7 +187,7 @@ calculate_server_args() {
181
187
  haproxy_config=$MRSS_ROOT/share/haproxy-1.conf
182
188
  else
183
189
  args="$args --mongos 2"
184
- haproxy_config=$MRSS_ROOT/share/haproxy-1.conf
190
+ haproxy_config=$MRSS_ROOT/share/haproxy-2.conf
185
191
  fi
186
192
  uri_options="$uri_options&loadBalanced=true"
187
193
  else
@@ -209,7 +215,6 @@ calculate_server_args() {
209
215
  fi
210
216
  fi
211
217
 
212
- local server_cert_path server_ca_path server_client_cert_path
213
218
  if test "$SSL" = ssl || test -n "$OCSP_ALGORITHM"; then
214
219
  if test -n "$OCSP_ALGORITHM"; then
215
220
  if test "$OCSP_MUST_STAPLE" = 1; then
@@ -288,7 +293,7 @@ calculate_server_args() {
288
293
  ocsp_args="$ocsp_args --fault $OCSP_STATUS"
289
294
  fi
290
295
  fi
291
-
296
+
292
297
  OCSP_ARGS="$ocsp_args"
293
298
  SERVER_ARGS="$args"
294
299
  URI_OPTIONS="$uri_options"
@@ -306,12 +311,57 @@ launch_server() {
306
311
  local dbdir="$1"
307
312
  python -m mtools.mlaunch.mlaunch --dir "$dbdir" --binarypath "$BINDIR" $SERVER_ARGS
308
313
 
314
+ if test "$TOPOLOGY" = sharded-cluster && test $MONGODB_VERSION = 3.6; then
315
+ # On 3.6 server the sessions collection is not immediately available,
316
+ # so we run the refreshLogicalSessionCacheNow command on the config server
317
+ # and again on each mongos in order for the mongoses
318
+ # to correctly report logicalSessionTimeoutMinutes.
319
+ mongos_regex="\s*mongos\s+([0-9]+)\s+running\s+[0-9]+"
320
+ config_server_regex="\s*config\sserver\s+([0-9]+)\s+running\s+[0-9]+"
321
+ config_server=""
322
+ mongoses=()
323
+ if test "$AUTH" = auth
324
+ then
325
+ base_url="mongodb://bob:pwd123@localhost"
326
+ else
327
+ base_url="mongodb://localhost"
328
+ fi
329
+ if test "$SSL" = "ssl"
330
+ then
331
+ mongo_command="${BINDIR}/mongo --ssl --sslPEMKeyFile $server_cert_path --sslCAFile $server_ca_path"
332
+ else
333
+ mongo_command="${BINDIR}/mongo"
334
+ fi
335
+
336
+ while read -r line
337
+ do
338
+ if [[ $line =~ $config_server_regex ]]
339
+ then
340
+ port="${BASH_REMATCH[1]}"
341
+ config_server="${base_url}:${port}"
342
+ fi
343
+ if [[ $line =~ $mongos_regex ]]
344
+ then
345
+ port="${BASH_REMATCH[1]}"
346
+ mongoses+=("${base_url}:${port}")
347
+ fi
348
+ done < <(python -m mtools.mlaunch.mlaunch list --dir "$dbdir" --binarypath "$BINDIR")
349
+
350
+ if [ -n "$config_server" ]; then
351
+ ${mongo_command} "$config_server" --eval 'db.adminCommand("refreshLogicalSessionCacheNow")'
352
+ for mongos in ${mongoses[*]}
353
+ do
354
+ ${mongo_command} "$mongos" --eval 'db.adminCommand("refreshLogicalSessionCacheNow")'
355
+ done
356
+ fi
357
+ fi
358
+
309
359
  if test "$TOPOLOGY" = load-balanced; then
310
360
  if test -z "$haproxy_config"; then
311
361
  echo haproxy_config should have been set 1>&2
312
362
  exit 3
313
363
  fi
314
-
364
+
315
365
  haproxy -D -f $haproxy_config -p $mongodb_dir/haproxy.pid
316
366
  fi
317
367
  }