thinking-sphinx 1.5.0 → 2.0.0.rc1

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 (104) hide show
  1. data/README.textile +15 -48
  2. data/VERSION +1 -0
  3. data/features/attribute_transformation.feature +7 -7
  4. data/features/attribute_updates.feature +16 -18
  5. data/features/deleting_instances.feature +13 -16
  6. data/features/excerpts.feature +0 -8
  7. data/features/facets.feature +19 -25
  8. data/features/handling_edits.feature +20 -25
  9. data/features/searching_across_models.feature +1 -1
  10. data/features/searching_by_index.feature +5 -6
  11. data/features/searching_by_model.feature +29 -29
  12. data/features/sphinx_scopes.feature +0 -26
  13. data/features/step_definitions/common_steps.rb +6 -18
  14. data/features/step_definitions/scope_steps.rb +0 -4
  15. data/features/step_definitions/search_steps.rb +4 -9
  16. data/features/support/env.rb +10 -3
  17. data/features/thinking_sphinx/db/fixtures/alphas.rb +10 -8
  18. data/features/thinking_sphinx/db/fixtures/cats.rb +1 -1
  19. data/features/thinking_sphinx/db/fixtures/dogs.rb +1 -1
  20. data/features/thinking_sphinx/db/fixtures/foxes.rb +1 -1
  21. data/features/thinking_sphinx/db/fixtures/people.rb +1 -1
  22. data/features/thinking_sphinx/db/fixtures/posts.rb +1 -5
  23. data/features/thinking_sphinx/db/migrations/create_posts.rb +0 -1
  24. data/features/thinking_sphinx/models/alpha.rb +0 -1
  25. data/features/thinking_sphinx/models/beta.rb +0 -5
  26. data/features/thinking_sphinx/models/developer.rb +1 -6
  27. data/features/thinking_sphinx/models/music.rb +1 -3
  28. data/features/thinking_sphinx/models/person.rb +1 -2
  29. data/features/thinking_sphinx/models/post.rb +0 -1
  30. data/lib/cucumber/thinking_sphinx/external_world.rb +4 -8
  31. data/lib/cucumber/thinking_sphinx/internal_world.rb +27 -36
  32. data/lib/thinking_sphinx.rb +60 -132
  33. data/lib/thinking_sphinx/active_record.rb +98 -124
  34. data/lib/thinking_sphinx/active_record/attribute_updates.rb +13 -17
  35. data/lib/thinking_sphinx/active_record/delta.rb +15 -21
  36. data/lib/thinking_sphinx/active_record/has_many_association.rb +23 -16
  37. data/lib/thinking_sphinx/active_record/scopes.rb +0 -18
  38. data/lib/thinking_sphinx/adapters/abstract_adapter.rb +15 -63
  39. data/lib/thinking_sphinx/adapters/mysql_adapter.rb +0 -4
  40. data/lib/thinking_sphinx/adapters/postgresql_adapter.rb +24 -65
  41. data/lib/thinking_sphinx/association.rb +11 -36
  42. data/lib/thinking_sphinx/attribute.rb +85 -92
  43. data/lib/thinking_sphinx/auto_version.rb +3 -21
  44. data/lib/thinking_sphinx/class_facet.rb +3 -8
  45. data/lib/thinking_sphinx/configuration.rb +58 -114
  46. data/lib/thinking_sphinx/context.rb +20 -22
  47. data/lib/thinking_sphinx/core/array.rb +13 -0
  48. data/lib/thinking_sphinx/deltas.rb +0 -2
  49. data/lib/thinking_sphinx/deltas/default_delta.rb +22 -18
  50. data/lib/thinking_sphinx/deploy/capistrano.rb +31 -30
  51. data/lib/thinking_sphinx/excerpter.rb +1 -2
  52. data/lib/thinking_sphinx/facet.rb +35 -45
  53. data/lib/thinking_sphinx/facet_search.rb +24 -58
  54. data/lib/thinking_sphinx/field.rb +0 -18
  55. data/lib/thinking_sphinx/index.rb +36 -38
  56. data/lib/thinking_sphinx/index/builder.rb +59 -74
  57. data/lib/thinking_sphinx/property.rb +45 -66
  58. data/lib/thinking_sphinx/railtie.rb +35 -0
  59. data/lib/thinking_sphinx/search.rb +250 -506
  60. data/lib/thinking_sphinx/source.rb +31 -50
  61. data/lib/thinking_sphinx/source/internal_properties.rb +3 -8
  62. data/lib/thinking_sphinx/source/sql.rb +31 -71
  63. data/lib/thinking_sphinx/tasks.rb +27 -48
  64. data/spec/thinking_sphinx/active_record/delta_spec.rb +41 -36
  65. data/spec/thinking_sphinx/active_record/has_many_association_spec.rb +0 -96
  66. data/spec/thinking_sphinx/active_record/scopes_spec.rb +29 -29
  67. data/spec/thinking_sphinx/active_record_spec.rb +169 -140
  68. data/spec/thinking_sphinx/association_spec.rb +2 -20
  69. data/spec/thinking_sphinx/attribute_spec.rb +97 -101
  70. data/spec/thinking_sphinx/auto_version_spec.rb +11 -75
  71. data/spec/thinking_sphinx/configuration_spec.rb +62 -63
  72. data/spec/thinking_sphinx/context_spec.rb +66 -66
  73. data/spec/thinking_sphinx/facet_search_spec.rb +99 -99
  74. data/spec/thinking_sphinx/facet_spec.rb +4 -30
  75. data/spec/thinking_sphinx/field_spec.rb +3 -17
  76. data/spec/thinking_sphinx/index/builder_spec.rb +132 -169
  77. data/spec/thinking_sphinx/index_spec.rb +39 -45
  78. data/spec/thinking_sphinx/search_methods_spec.rb +33 -37
  79. data/spec/thinking_sphinx/search_spec.rb +269 -491
  80. data/spec/thinking_sphinx/source_spec.rb +48 -62
  81. data/spec/thinking_sphinx_spec.rb +49 -49
  82. data/tasks/distribution.rb +46 -0
  83. data/tasks/testing.rb +74 -0
  84. metadata +123 -199
  85. data/features/field_sorting.feature +0 -18
  86. data/features/thinking_sphinx/db/.gitignore +0 -1
  87. data/features/thinking_sphinx/db/fixtures/post_keywords.txt +0 -1
  88. data/features/thinking_sphinx/models/andrew.rb +0 -17
  89. data/lib/thinking-sphinx.rb +0 -1
  90. data/lib/thinking_sphinx/active_record/has_many_association_with_scopes.rb +0 -21
  91. data/lib/thinking_sphinx/bundled_search.rb +0 -40
  92. data/lib/thinking_sphinx/connection.rb +0 -71
  93. data/lib/thinking_sphinx/deltas/delete_job.rb +0 -16
  94. data/lib/thinking_sphinx/deltas/index_job.rb +0 -17
  95. data/lib/thinking_sphinx/rails_additions.rb +0 -181
  96. data/spec/fixtures/data.sql +0 -32
  97. data/spec/fixtures/database.yml.default +0 -3
  98. data/spec/fixtures/models.rb +0 -161
  99. data/spec/fixtures/structure.sql +0 -146
  100. data/spec/spec_helper.rb +0 -54
  101. data/spec/sphinx_helper.rb +0 -67
  102. data/spec/thinking_sphinx/adapters/abstract_adapter_spec.rb +0 -163
  103. data/spec/thinking_sphinx/connection_spec.rb +0 -77
  104. data/spec/thinking_sphinx/rails_additions_spec.rb +0 -203
@@ -7,7 +7,7 @@ Feature: Searching across multiple model
7
7
  Given Sphinx is running
8
8
  When I search for James
9
9
  And I am retrieving the result count
10
- Then I should get a value of 6
10
+ Then I should get a value of 3
11
11
 
12
12
  Scenario: Confirming existance of a document id in a given index
13
13
  Given Sphinx is running
@@ -2,21 +2,21 @@ Feature: Searching within a single index
2
2
  In order to use Thinking Sphinx's core functionality
3
3
  A developer
4
4
  Should be able to search on a single index
5
-
5
+
6
6
  Scenario: Searching with alternative index
7
7
  Given Sphinx is running
8
8
  And I am searching on alphas
9
9
  When I order by value
10
10
  And I use index alternative_core
11
11
  Then I should get 7 results
12
-
12
+
13
13
  Scenario: Searching with default index
14
14
  Given Sphinx is running
15
15
  And I am searching on alphas
16
16
  When I order by value
17
17
  And I use index alpha_core
18
18
  Then I should get 10 results
19
-
19
+
20
20
  Scenario: Searching without specified index
21
21
  Given Sphinx is running
22
22
  And I am searching on alphas
@@ -26,15 +26,14 @@ Feature: Searching within a single index
26
26
  Scenario: Deleting instances from the core index
27
27
  Given Sphinx is running
28
28
  And I am searching on alphas
29
-
29
+
30
30
  When I create a new alpha named eleven
31
31
  And I process the alpha_core index
32
32
  And I process the alternative_core index
33
33
  And I wait for Sphinx to catch up
34
- And I clear the connection pool
35
34
  And I search for eleven
36
35
  Then I should get 1 result
37
-
36
+
38
37
  When I destroy alpha eleven
39
38
  And I wait for Sphinx to catch up
40
39
  And I search for eleven
@@ -2,19 +2,19 @@ Feature: Searching on a single model
2
2
  In order to use Thinking Sphinx's core functionality
3
3
  A developer
4
4
  Should be able to search on a single model
5
-
5
+
6
6
  Scenario: Searching using a basic query
7
7
  Given Sphinx is running
8
8
  And I am searching on people
9
9
  When I search for James
10
10
  Then I should get 3 results
11
-
11
+
12
12
  Scenario: Searching on a specific field
13
13
  Given Sphinx is running
14
14
  And I am searching on people
15
15
  When I search for James on first_name
16
16
  Then I should get 2 results
17
-
17
+
18
18
  Scenario: Searching on multiple fields
19
19
  Given Sphinx is running
20
20
  And I am searching on people
@@ -25,32 +25,32 @@ Feature: Searching on a single model
25
25
  Scenario: Searching on association content
26
26
  Given Sphinx is running
27
27
  And I am searching on posts
28
-
28
+
29
29
  When I search for "Waffles"
30
30
  Then I should get 1 result
31
31
 
32
32
  When I search for "Turtle"
33
33
  Then I should get 1 result
34
-
34
+
35
35
  Scenario: Searching with a filter
36
36
  Given Sphinx is running
37
37
  And I am searching on alphas
38
38
  When I filter by 1 on value
39
39
  Then I should get 1 result
40
-
40
+
41
41
  Scenario: Searching with multiple filters
42
42
  Given Sphinx is running
43
43
  And I am searching on boxes
44
44
  When I filter by 2 on width
45
45
  And I filter by 2 on length
46
46
  Then I should get 1 result
47
-
47
+
48
48
  Scenario: Searching with a ranged time filter
49
49
  Given Sphinx is running
50
50
  And I am searching on people
51
51
  When I filter by birthday between 1975 and 1976
52
52
  Then I should get 16 results
53
-
53
+
54
54
  Scenario: Searching to filter multiple values on an MVA
55
55
  Given Sphinx is running
56
56
  And I am searching on boxes
@@ -59,25 +59,25 @@ Feature: Searching on a single model
59
59
  When I clear existing filters
60
60
  And I filter by both 11 and 12 on dimensions
61
61
  Then I should get 1 result
62
-
62
+
63
63
  Scenario: Filtering on timestamp MVAs
64
64
  Given Sphinx is running
65
65
  And I am searching on posts
66
66
  When I filter by 2001-01-01 on comments_created_at
67
67
  Then I should get 1 result
68
-
68
+
69
69
  Scenario: Searching by NULL/0 values in MVAs
70
70
  Given Sphinx is running
71
71
  And I am searching on boxes
72
72
  When I filter by 0 on dimensions
73
73
  Then I should get 1 result
74
-
74
+
75
75
  Given Sphinx is running
76
76
  And I am searching on developers
77
77
  When I clear existing filters
78
78
  And I filter by 0 on tag_ids
79
79
  Then I should get 1 result
80
-
80
+
81
81
  Scenario: Searching on a MVA configured as ranged_query
82
82
  Given Sphinx is running
83
83
  And I am searching on posts
@@ -89,59 +89,66 @@ Feature: Searching on a single model
89
89
  When I clear existing filters
90
90
  And I filter by 10 on comment_ids
91
91
  Then I should get 0 results
92
-
92
+
93
93
  Scenario: Searching with ordering by attribute
94
94
  Given Sphinx is running
95
95
  And I am searching on alphas
96
96
  When I order by value
97
97
  Then I should get 10 results
98
98
  And the value of each result should indicate order
99
-
99
+
100
+ Scenario: Searching with ordering on a sortable field
101
+ Given Sphinx is running
102
+ And I am searching on people
103
+ And I order by first_name
104
+ Then I should get 20 results
105
+ And the first_name of each result should indicate order
106
+
100
107
  Scenario: Intepreting Sphinx Internal Identifiers
101
108
  Given Sphinx is running
102
109
  And I am searching on people
103
110
  Then I should get 20 results
104
111
  And each result id should match the corresponding sphinx internal id
105
-
112
+
106
113
  Scenario: Retrieving weightings
107
114
  Given Sphinx is running
108
115
  And I am searching on people
109
116
  When I search for "Ellie Ford"
110
117
  And I set match mode to any
111
118
  Then I can iterate by result and weighting
112
-
119
+
113
120
  Scenario: Retrieving group counts
114
121
  Given Sphinx is running
115
122
  And I am searching on people
116
123
  When I group results by the birthday attribute
117
124
  Then I can iterate by result and count
118
-
125
+
119
126
  Scenario: Retrieving group values
120
127
  Given Sphinx is running
121
128
  And I am searching on people
122
129
  When I group results by the birthday attribute
123
130
  Then I can iterate by result and group
124
-
131
+
125
132
  Scenario: Retrieving both group values and counts
126
133
  Given Sphinx is running
127
134
  And I am searching on people
128
135
  When I group results by the birthday attribute
129
136
  Then I can iterate by result and group and count
130
-
137
+
131
138
  Scenario: Searching for ids
132
139
  Given Sphinx is running
133
140
  And I am searching on people
134
141
  When I search for Ellie
135
142
  And I am searching for ids
136
143
  Then I should have an array of integers
137
-
144
+
138
145
  Scenario: Search results should match Sphinx's order
139
146
  Given Sphinx is running
140
147
  And I am searching on people
141
148
  When I search for Ellie
142
149
  And I order by "sphinx_internal_id DESC"
143
150
  Then searching for ids should match the record ids of the normal search results
144
-
151
+
145
152
  Scenario: Retrieving total result count when total is less than a page
146
153
  Given Sphinx is running
147
154
  And I am searching on people
@@ -154,7 +161,7 @@ Feature: Searching on a single model
154
161
  And I am searching on people
155
162
  When I am retrieving the result count
156
163
  Then I should get a value of 1000
157
-
164
+
158
165
  Scenario: Searching with Unicode Characters
159
166
  Given Sphinx is running
160
167
  And I am searching on people
@@ -166,10 +173,3 @@ Feature: Searching on a single model
166
173
  And I am searching on posts
167
174
  When I search for "Shakespeare"
168
175
  Then I should get 1 result
169
-
170
- Scenario: Searching on content from file field
171
- Given Sphinx is running
172
- And I am searching on posts
173
- When I search for "foo bar baz"
174
- Then I should get 1 result
175
-
@@ -40,29 +40,3 @@ Feature: Sphinx Scopes
40
40
  When I use the with_first_name scope set to "Andrew"
41
41
  And I am retrieving the scoped result count
42
42
  Then I should get a value of 7
43
-
44
- Scenario: Counts with scopes and additional query terms
45
- Given Sphinx is running
46
- And I am searching on people
47
- When I use the with_first_name scope set to "Andrew"
48
- And I am retrieving the scoped result count for "Byrne"
49
- Then I should get a value of 1
50
-
51
- Scenario: Default Scope
52
- Given Sphinx is running
53
- And I am searching on andrews
54
- Then I should get 7 results
55
-
56
- Scenario: Default Scope and additional query terms
57
- Given Sphinx is running
58
- And I am searching on andrews
59
- When I search for "Byrne"
60
- Then I should get 1 result
61
-
62
- Scenario: Explicit scope plus search over a default scope
63
- Given Sphinx is running
64
- And I am searching on andrews
65
- When I use the locked_last_name scope
66
- And I search for "Cecil"
67
- Then I should get 1 result
68
-
@@ -1,6 +1,6 @@
1
1
  Before do
2
2
  $queries_executed = []
3
-
3
+
4
4
  @model = nil
5
5
  @method = :search
6
6
  @query = ""
@@ -10,7 +10,7 @@ Before do
10
10
  @with_all = {}
11
11
  @options = {}
12
12
  @results = nil
13
-
13
+
14
14
  Given "updates are enabled"
15
15
  end
16
16
 
@@ -46,10 +46,6 @@ When /^I search for (\w+)$/ do |query|
46
46
  @query = query
47
47
  end
48
48
 
49
- When /^I clear the connection pool$/ do
50
- ThinkingSphinx::Connection.pool.clear
51
- end
52
-
53
49
  When /^I search for "([^\"]*)"$/ do |query|
54
50
  @results = nil
55
51
  @query = query
@@ -99,13 +95,9 @@ When /^I filter between ([\d\.]+) and ([\d\.]+) on (\w+)$/ do |first, last, attr
99
95
  end
100
96
  end
101
97
 
102
- When /^I filter between (\d+) and (\d+) days ago on (\w+)(?: by (date))?$/ do |last, first, attribute, by_date|
98
+ When /^I filter between (\d+) and (\d+) days ago on (\w+)$/ do |last, first, attribute|
103
99
  @results = nil
104
-
105
- first, last = first.to_i.days.ago, last.to_i.days.ago
106
- first, last = first.to_date, last.to_date if by_date
107
-
108
- @with[attribute.to_sym] = first..last
100
+ @with[attribute.to_sym] = first.to_i.days.ago..last.to_i.days.ago
109
101
  end
110
102
 
111
103
  When /^I filter by (\w+) between (\d+) and (\d+)$/ do |attribute, first, last|
@@ -158,15 +150,11 @@ Then /^the (\w+) of each result should indicate order$/ do |attribute|
158
150
  unless prev.nil?
159
151
  current.send(attribute.to_sym).should >= prev.send(attribute.to_sym)
160
152
  end
161
-
153
+
162
154
  current
163
155
  end
164
156
  end
165
157
 
166
- Then /^the first result's "([^"]*)" should be "([^"]*)"$/ do |attribute, value|
167
- results.first.send(attribute.to_sym).should == value
168
- end
169
-
170
158
  Then /^I can iterate by result and (\w+)$/ do |attribute|
171
159
  iteration = lambda { |result, attr_value|
172
160
  result.should be_kind_of(@model)
@@ -174,7 +162,7 @@ Then /^I can iterate by result and (\w+)$/ do |attribute|
174
162
  attr_value.should be_kind_of(Integer)
175
163
  end
176
164
  }
177
-
165
+
178
166
  results.send("each_with_#{attribute}", &iteration)
179
167
  end
180
168
 
@@ -13,7 +13,3 @@ end
13
13
  When /^I am retrieving the scoped result count$/ do
14
14
  @results = results.search_count
15
15
  end
16
-
17
- When /^I am retrieving the scoped result count for "([^"]*)"$/ do |query|
18
- @results = results.search_count query
19
- end
@@ -11,24 +11,24 @@ When /^I search for the document id of (\w+) (\w+) in the (\w+) index$/ do |mode
11
11
  end
12
12
 
13
13
  Then "it should exist" do
14
- ThinkingSphinx.search_for_id(@id, @index).should == true
14
+ ThinkingSphinx::Search.search_for_id(@id, @index).should == true
15
15
  end
16
16
 
17
17
  Then "it should not exist" do
18
- ThinkingSphinx.search_for_id(@id, @index).should == false
18
+ ThinkingSphinx::Search.search_for_id(@id, @index).should == false
19
19
  end
20
20
 
21
21
  Then "it should exist if using Rails 2.1 or newer" do
22
22
  require 'active_record/version'
23
23
  unless ActiveRecord::VERSION::STRING.to_f < 2.1
24
- ThinkingSphinx.search_for_id(@id, @index).should == true
24
+ ThinkingSphinx::Search.search_for_id(@id, @index).should == true
25
25
  end
26
26
  end
27
27
 
28
28
  Then "it should not exist if using Rails 2.1 or newer" do
29
29
  require 'active_record/version'
30
30
  unless ActiveRecord::VERSION::STRING.to_f < 2.1
31
- ThinkingSphinx.search_for_id(@id, @index).should == false
31
+ ThinkingSphinx::Search.search_for_id(@id, @index).should == false
32
32
  end
33
33
  end
34
34
 
@@ -87,8 +87,3 @@ end
87
87
  Then /^the first result should have a (\w+\s?\w*) of (\d+)$/ do |attribute, value|
88
88
  results.first.sphinx_attributes[attribute.gsub(/\s+/, '_')].should == value.to_i
89
89
  end
90
-
91
- Given /^I provide excerpt option "([a-z_]*)" with value "([^"]*)"$/ do |k, v|
92
- @options[:excerpt_options] ||= {}
93
- @options[:excerpt_options][k.to_sym] = v
94
- end
@@ -1,10 +1,15 @@
1
1
  require 'rubygems'
2
+ require 'cucumber'
3
+ require 'rspec'
2
4
  require 'fileutils'
3
- require 'bundler'
4
-
5
- Bundler.require :default, :development
5
+ require 'ginger'
6
+ require 'will_paginate'
7
+ require 'active_record'
6
8
 
7
9
  $:.unshift File.dirname(__FILE__) + '/../../lib'
10
+ Dir[File.join(File.dirname(__FILE__), '../../vendor/*/lib')].each do |path|
11
+ $:.unshift path
12
+ end
8
13
 
9
14
  require 'cucumber/thinking_sphinx/internal_world'
10
15
 
@@ -13,4 +18,6 @@ world.configure_database
13
18
 
14
19
  require "thinking_sphinx"
15
20
 
21
+ ActiveRecord::Base.send(:include, ThinkingSphinx::ActiveRecord)
22
+
16
23
  world.setup
@@ -1,8 +1,10 @@
1
- %w(
2
- one two three four five six seven eight nine ten
3
- ).each_with_index do |number, index|
4
- value = index + 1
5
- cost = value.to_f + 0.5 + (value * 0.01)
6
- Alpha.create :name => number, :value => value, :cost => cost,
7
- :created_on => value.days.ago.to_date, :created_at => value.days.ago
8
- end
1
+ Alpha.create :name => "one", :value => 1, :cost => 1.51, :created_on => 1.day.ago.to_date, :created_at => 1.day.ago
2
+ Alpha.create :name => "two", :value => 2, :cost => 2.52, :created_on => 2.day.ago.to_date, :created_at => 2.day.ago
3
+ Alpha.create :name => "three", :value => 3, :cost => 3.53, :created_on => 3.day.ago.to_date, :created_at => 3.day.ago
4
+ Alpha.create :name => "four", :value => 4, :cost => 4.54, :created_on => 4.day.ago.to_date, :created_at => 4.day.ago
5
+ Alpha.create :name => "five", :value => 5, :cost => 5.55, :created_on => 5.day.ago.to_date, :created_at => 5.day.ago
6
+ Alpha.create :name => "six", :value => 6, :cost => 6.56, :created_on => 6.day.ago.to_date, :created_at => 6.day.ago
7
+ Alpha.create :name => "seven", :value => 7, :cost => 7.57, :created_on => 7.day.ago.to_date, :created_at => 7.day.ago
8
+ Alpha.create :name => "eight", :value => 8, :cost => 8.58, :created_on => 8.day.ago.to_date, :created_at => 8.day.ago
9
+ Alpha.create :name => "nine", :value => 9, :cost => 9.59, :created_on => 9.day.ago.to_date, :created_at => 9.day.ago
10
+ Alpha.create :name => "ten", :value => 10, :cost => 10.50, :created_on => 10.day.ago.to_date, :created_at => 10.day.ago
@@ -1,3 +1,3 @@
1
1
  %w( rogue nat molly jasper moggy ).each do |name|
2
- Cat.new(:name => name).save(false)
2
+ Cat.create :name => name
3
3
  end
@@ -1,3 +1,3 @@
1
1
  %w( rover lassie gaspode ).each do |name|
2
- Dog.new(:name => name).save(false)
2
+ Dog.create :name => name
3
3
  end
@@ -1,3 +1,3 @@
1
1
  %w( fantastic ).each do |name|
2
- Fox.new(:name => name).save(false)
2
+ Fox.create :name => name
3
3
  end