marklogic 0.0.1

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 (90) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +10 -0
  3. data/.gitignore +15 -0
  4. data/.rspec +2 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/Gemfile +17 -0
  8. data/Guardfile +45 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +31 -0
  11. data/Rakefile +6 -0
  12. data/lib/marklogic.rb +21 -0
  13. data/lib/marklogic/app_server.rb +60 -0
  14. data/lib/marklogic/application.rb +244 -0
  15. data/lib/marklogic/collection.rb +265 -0
  16. data/lib/marklogic/connection.rb +308 -0
  17. data/lib/marklogic/consts.rb +35 -0
  18. data/lib/marklogic/cursor.rb +238 -0
  19. data/lib/marklogic/database.rb +205 -0
  20. data/lib/marklogic/database_settings.rb +13 -0
  21. data/lib/marklogic/database_settings/element_word_lexicon.rb +28 -0
  22. data/lib/marklogic/database_settings/geospatial_element_child_index.rb +41 -0
  23. data/lib/marklogic/database_settings/geospatial_element_index.rb +38 -0
  24. data/lib/marklogic/database_settings/geospatial_element_pair_index.rb +42 -0
  25. data/lib/marklogic/database_settings/geospatial_path_index.rb +37 -0
  26. data/lib/marklogic/database_settings/index.rb +27 -0
  27. data/lib/marklogic/database_settings/range_element_index.rb +77 -0
  28. data/lib/marklogic/database_settings/range_field_index.rb +37 -0
  29. data/lib/marklogic/database_settings/range_path_index.rb +37 -0
  30. data/lib/marklogic/exceptions.rb +5 -0
  31. data/lib/marklogic/forest.rb +47 -0
  32. data/lib/marklogic/loggable.rb +46 -0
  33. data/lib/marklogic/object_id.rb +46 -0
  34. data/lib/marklogic/persistence.rb +29 -0
  35. data/lib/marklogic/queries.rb +18 -0
  36. data/lib/marklogic/queries/and_not_query.rb +14 -0
  37. data/lib/marklogic/queries/and_query.rb +14 -0
  38. data/lib/marklogic/queries/base_query.rb +40 -0
  39. data/lib/marklogic/queries/boost_query.rb +14 -0
  40. data/lib/marklogic/queries/collection_query.rb +14 -0
  41. data/lib/marklogic/queries/container_query.rb +15 -0
  42. data/lib/marklogic/queries/directory_query.rb +20 -0
  43. data/lib/marklogic/queries/document_fragment_query.rb +13 -0
  44. data/lib/marklogic/queries/document_query.rb +14 -0
  45. data/lib/marklogic/queries/geospatial_query.rb +44 -0
  46. data/lib/marklogic/queries/locks_fragment_query.rb +13 -0
  47. data/lib/marklogic/queries/near_query.rb +31 -0
  48. data/lib/marklogic/queries/not_in_query.rb +14 -0
  49. data/lib/marklogic/queries/not_query.rb +13 -0
  50. data/lib/marklogic/queries/or_query.rb +24 -0
  51. data/lib/marklogic/queries/properties_fragment_query.rb +13 -0
  52. data/lib/marklogic/queries/range_query.rb +67 -0
  53. data/lib/marklogic/queries/value_query.rb +44 -0
  54. data/lib/marklogic/queries/word_query.rb +38 -0
  55. data/lib/marklogic/version.rb +3 -0
  56. data/marklogic.gemspec +23 -0
  57. data/spec/marklogic/app_server_spec.rb +21 -0
  58. data/spec/marklogic/application_spec.rb +105 -0
  59. data/spec/marklogic/collection_spec.rb +154 -0
  60. data/spec/marklogic/connection_spec.rb +128 -0
  61. data/spec/marklogic/cursor_spec.rb +219 -0
  62. data/spec/marklogic/database_settings/element_word_lexicon_spec.rb +21 -0
  63. data/spec/marklogic/database_settings/geospatial_element_child_index_spec.rb +26 -0
  64. data/spec/marklogic/database_settings/geospatial_element_index_spec.rb +24 -0
  65. data/spec/marklogic/database_settings/geospatial_element_pair_index_spec.rb +27 -0
  66. data/spec/marklogic/database_settings/geospatial_path_index_spec.rb +23 -0
  67. data/spec/marklogic/database_settings/range_element_index_spec.rb +34 -0
  68. data/spec/marklogic/database_settings/range_field_index_spec.rb +23 -0
  69. data/spec/marklogic/database_settings/range_path_index_spec.rb +23 -0
  70. data/spec/marklogic/database_spec.rb +108 -0
  71. data/spec/marklogic/forest_spec.rb +30 -0
  72. data/spec/marklogic/queries/and_not_query_spec.rb +13 -0
  73. data/spec/marklogic/queries/and_query_spec.rb +31 -0
  74. data/spec/marklogic/queries/boost_query_spec.rb +13 -0
  75. data/spec/marklogic/queries/collection_query_spec.rb +16 -0
  76. data/spec/marklogic/queries/container_query_spec.rb +11 -0
  77. data/spec/marklogic/queries/directory_query_spec.rb +21 -0
  78. data/spec/marklogic/queries/document_fragment_query_spec.rb +11 -0
  79. data/spec/marklogic/queries/document_query_spec.rb +16 -0
  80. data/spec/marklogic/queries/locks_fragement_query_spec.rb +11 -0
  81. data/spec/marklogic/queries/near_query_spec.rb +62 -0
  82. data/spec/marklogic/queries/not_in_query_spec.rb +13 -0
  83. data/spec/marklogic/queries/not_query_spec.rb +11 -0
  84. data/spec/marklogic/queries/or_query_spec.rb +32 -0
  85. data/spec/marklogic/queries/properties_fragment_query_spec.rb +11 -0
  86. data/spec/marklogic/queries/range_query_spec.rb +71 -0
  87. data/spec/marklogic/queries/value_query_spec.rb +68 -0
  88. data/spec/marklogic/queries/word_query_spec.rb +53 -0
  89. data/spec/spec_helper.rb +68 -0
  90. metadata +186 -0
@@ -0,0 +1,35 @@
1
+ module MarkLogic
2
+
3
+ ROOT_COLLATION = "http://marklogic.com/collation/"
4
+ CODEPOINT_COLLATION = "http://marklogic.com/collation/codepoint"
5
+ DEFAULT_COLLATION = ROOT_COLLATION
6
+
7
+ GEO_WGS84 = "wgs84"
8
+ GEO_RAW = "raw"
9
+
10
+ POINT = "point"
11
+ LONG_LAT_POINT = "long-lat-point"
12
+
13
+ INT = "int"
14
+ UNSIGNED_INT = "unsignedInt"
15
+ LONG = "long"
16
+ UNSIGNED_LONG = "unsignedLong"
17
+ FLOAT = "float"
18
+ DOUBLE = "double"
19
+ DECIMAL = "decimal"
20
+ DATE_TIME = "dateTime"
21
+ TIME = "time"
22
+ DATE = "date"
23
+ G_YEAR_MONTH = "gYearMonth"
24
+ G_YEAR = "gYear"
25
+ G_MONTH = "gMonth"
26
+ G_DAY = "gDay"
27
+ YEAR_MONTH_DURATION = "yearMonthDuration"
28
+ DAY_TIME_DURATION = "dayTimeDuration"
29
+ STRING = "string"
30
+ ANY_URI = "anyURI"
31
+
32
+ IGNORE = "ignore"
33
+ REJECT = "reject"
34
+
35
+ end
@@ -0,0 +1,238 @@
1
+ require 'pp'
2
+ module MarkLogic
3
+ class Cursor
4
+ include Enumerable
5
+
6
+ attr_accessor :queries
7
+
8
+ DEFAULT_PAGE_LENGTH = 25
9
+
10
+ def initialize(collection, options = {})
11
+ @options = options || {}
12
+ @query = options.delete(:query)
13
+ @collection = collection
14
+ @transformer = options.delete(:transformer)
15
+ @fields = options.delete(:fields)
16
+ @fields = convert_fields_for_query(@fields) if @fields
17
+ @connection = @collection.database.connection
18
+ @limit = options.delete(:limit)
19
+ @cache = []
20
+ @query_run = false
21
+ @visited = 0
22
+ end
23
+
24
+ def count
25
+ col_name = collection.nil? ? "" : %Q{"#{collection.collection}"}
26
+ query_to_run = %Q{xdmp:estimate(cts:search(fn:collection(#{col_name}), #{query.to_xqy}, ("unfiltered")))}
27
+ response = @connection.run_query(query_to_run, "xquery")
28
+ raise Exception.new("Invalid response: #{response.code.to_i}: #{response.body}") if (response.code.to_i != 200)
29
+ response.body.to_i
30
+ end
31
+
32
+ def paged_results
33
+ refresh unless @query_run
34
+ @cache
35
+ end
36
+
37
+ def next
38
+ refresh unless @query_run
39
+
40
+ return nil if @visited == @limit
41
+
42
+ if @cache.length == 0
43
+ if page < total_pages
44
+ self.page = page + 1
45
+ refresh
46
+ end
47
+
48
+ if @cache.length == 0
49
+ return nil
50
+ end
51
+ end
52
+
53
+ @visited = @visited + 1
54
+ doc = @cache.shift
55
+
56
+ # TODO: Move this server side
57
+ if @fields
58
+ @fields << :_id unless @fields.include?(:_id) || @fields.include?('_id')
59
+ doc = @fields.each_with_object(doc.class.new) { |key, result| result[key] = doc[key.to_s] if doc.has_key?(key.to_s) }
60
+ end
61
+
62
+ if @transformer.nil?
63
+ doc
64
+ else
65
+ @transformer.call(doc) if doc
66
+ end
67
+ end
68
+ alias_method :next_document, :next
69
+
70
+ def convert_fields_for_query(fields)
71
+ case fields
72
+ when String, Symbol
73
+ [ fields ]
74
+ when Array
75
+ return nil if fields.length.zero?
76
+ fields
77
+ when Hash
78
+ return fields.keys
79
+ end
80
+ end
81
+
82
+ def each(&block)
83
+ while doc = self.next
84
+ yield doc
85
+ end
86
+ end
87
+
88
+ def to_a
89
+ super
90
+ end
91
+
92
+ def rewind!
93
+ self.page = 1
94
+ @query_run = false
95
+ @visited = 0
96
+ end
97
+
98
+ private
99
+
100
+ def start
101
+ if @options[:skip]
102
+ @options[:skip] + 1
103
+ else
104
+ (page - 1) * page_length + 1
105
+ end
106
+ end
107
+
108
+ def page
109
+ page = @options[:page] || 1
110
+ page.to_i
111
+ end
112
+
113
+ def page=(page)
114
+ @options[:page] = page.to_i
115
+ end
116
+
117
+ def page_length
118
+ @options[:per_page] || DEFAULT_PAGE_LENGTH
119
+ end
120
+
121
+ def total_pages
122
+ (count.to_f / page_length.to_f).ceil
123
+ end
124
+
125
+ def view
126
+ "none"
127
+ end
128
+
129
+ def format
130
+ @options[:format] || "json"
131
+ end
132
+
133
+ def collection
134
+ @collection
135
+ end
136
+
137
+ def return_results
138
+ @options[:return_results] || false
139
+ end
140
+
141
+ def return_metrics
142
+ @options[:return_metrics] || false
143
+ end
144
+
145
+ def return_qtext
146
+ @options[:return_qtext] || false
147
+ end
148
+
149
+ def return_query
150
+ @options[:return_query] || true
151
+ end
152
+
153
+ def debug
154
+ @options[:debug] || false
155
+ end
156
+
157
+ def has_sort?
158
+ @options.has_key?(:sort) or @options.has_key?(:order)
159
+ end
160
+
161
+ def query
162
+ @query || MarkLogic::Queries::AndQuery.new
163
+ end
164
+
165
+ def sort
166
+ return nil unless has_sort?
167
+
168
+ sorters = @options[:sort] || @options[:order]
169
+ sorters = [sorters] unless sorters.instance_of?(Array)
170
+
171
+ sorters.map do |sorter|
172
+ name = sorter[0].to_s
173
+ direction = (sorter[1] and (sorter[1] == -1)) ? "descending" : "ascending"
174
+
175
+
176
+ if @collection.database.has_range_index?(name)
177
+ index = @collection.database.range_index(name)
178
+ type = %Q{xs:#{index.scalar_type}}
179
+ collation = index.collation
180
+ else
181
+ raise MissingIndexError.new("Missing index on #{name}")
182
+ end
183
+ {
184
+ "direction" => direction || "ascending",
185
+ "type" => type,
186
+ "collation" => collation || "",
187
+ "json-property" => name
188
+ }
189
+ end
190
+ end
191
+
192
+ def sort_xqy
193
+ return %Q{cts:unordered()} unless has_sort?
194
+
195
+ sorters = @options[:sort] || @options[:order]
196
+ sorters = [sorters] unless sorters.instance_of?(Array)
197
+
198
+ sorters.map do |sorter|
199
+ name = sorter[0].to_s
200
+ direction = (sorter[1] and (sorter[1] == -1)) ? "descending" : "ascending"
201
+
202
+ unless @collection.database.has_range_index?(name)
203
+ raise MissingIndexError.new("Missing index on #{name}")
204
+ end
205
+
206
+ ref = @collection.database.range_index(name).to_ref
207
+
208
+ %Q{cts:index-order(#{ref}, "#{direction}")}
209
+ end.join(',')
210
+ end
211
+
212
+ def refresh
213
+ results = exec.body
214
+ if results.nil?
215
+ results = []
216
+ else
217
+ results = [results] unless results.instance_of?(Array)
218
+ end
219
+ @cache = results
220
+ end
221
+
222
+ def exec
223
+ query = to_xqy
224
+ response = @connection.run_query(query, "xquery")
225
+ raise Exception.new("Invalid response: #{response.code.to_i}: #{response.body}") if (response.code.to_i != 200)
226
+
227
+ @query_run = true
228
+ response
229
+ end
230
+
231
+ def to_xqy
232
+ start_index = start
233
+ end_index = start_index + page_length - 1
234
+ col_name = collection.nil? ? "" : %Q{"#{collection.collection}"}
235
+ %Q{(cts:search(fn:collection(#{col_name}), #{query.to_xqy}, ("unfiltered", "score-zero", #{sort_xqy})))[#{start_index} to #{end_index}]}
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,205 @@
1
+ require 'pp'
2
+
3
+ module MarkLogic
4
+ class Database
5
+ include MarkLogic::Persistence
6
+
7
+ INDEX_KEYS = [
8
+ 'range-element-index',
9
+ 'element-word-lexicon',
10
+ 'element-attribute-word-lexicon',
11
+ # 'path-namespace',
12
+ # 'field',
13
+ 'range-path-index',
14
+ 'range-field-index',
15
+ 'geospatial-element-index',
16
+ 'geospatial-element-child-index',
17
+ 'geospatial-element-pair-index',
18
+ 'geospatial-path-index'
19
+ ]
20
+
21
+ attr_accessor :database_name, :application
22
+ def initialize(database_name, conn = nil)
23
+ self.connection = conn
24
+
25
+ @database_name = database_name
26
+ @options = {
27
+ "database-name" => @database_name,
28
+ "collection-lexicon" => true
29
+ }
30
+
31
+ reset_indexes
32
+ end
33
+
34
+ def []=(key, value)
35
+ @options[key] = value
36
+ end
37
+
38
+ def [](key)
39
+ @options[key]
40
+ end
41
+
42
+ def has_key?(key)
43
+ @options.has_key?(key)
44
+ end
45
+
46
+ # def add_database_backup()
47
+ # @options["database-backups"] <<
48
+ # end
49
+
50
+ # def add_fragment_root()
51
+ # # @options["fragment-roots"] <<
52
+ # end
53
+
54
+ # def add_fragment_parent()
55
+ # # @options["fragment-parents"] <<
56
+ # end
57
+
58
+ # def add_element_word_query_through()
59
+ # # @options["element-word-query-throughs"] <<
60
+ # end
61
+
62
+ # def add_phrase_through()
63
+ # # @options["phrase-throughs"] <<
64
+ # end
65
+
66
+ def add_range_element_index(name, options = {})
67
+ add_index "range-element-index", MarkLogic::DatabaseSettings::RangeElementIndex.new(name, options)
68
+ end
69
+
70
+ def add_element_word_lexicon(localname, options)
71
+ add_index "element-word-lexicons", MarkLogic::DatabaseSettings::ElementWordLexicon.new(localname, options)
72
+ end
73
+
74
+ def add_path_namespace()
75
+ # add_index "path-namespace"
76
+ end
77
+
78
+ def add_range_path_index(path_expression, options = {})
79
+ add_index "range-path-index", MarkLogic::DatabaseSettings::RangePathIndex.new(path_expression, options)
80
+ end
81
+
82
+ def add_field()
83
+ # add_index "fields"
84
+ end
85
+
86
+ def add_range_field_index(field_name, options = {})
87
+ add_index "range-field-index", MarkLogic::DatabaseSettings::RangeFieldIndex.new(field_name, options)
88
+ end
89
+
90
+ def add_geospatial_element_index(element_name, latitude_localname, longitude_localname, options = {})
91
+ add_index "geospatial-element-index", MarkLogic::DatabaseSettings::GeospatialElementIndex.new(element_name, latitude_localname, longitude_localname, options)
92
+ end
93
+
94
+ def add_geospatial_element_child_index(element_name, latitude_localname, longitude_localname, options = {})
95
+ add_index "geospatial-element-child-index", MarkLogic::DatabaseSettings::GeospatialElementChildIndex.new(element_name, latitude_localname, longitude_localname, options)
96
+ end
97
+
98
+ def add_geospatial_element_pair_index(element_name, latitude_localname, longitude_localname, options = {})
99
+ add_index "geospatial-element-pair-index", MarkLogic::DatabaseSettings::GeospatialElementPairIndex(element_name, latitude_localname, longitude_localname, options)
100
+ end
101
+
102
+ def add_geospatial_path_index(path_expression, latitude_localname, longitude_localname, options = {})
103
+ add_index "geospatial-path-index", MarkLogic::DatabaseSettings::GeospatialPathIndex.new(path_expression, latitude_localname, longitude_localname, options)
104
+ end
105
+
106
+ # def add_foreign_database()
107
+ # add_index "foreign-database"
108
+ # end
109
+
110
+ def create
111
+ r = manage_connection.post_json(
112
+ %Q{/manage/v2/databases?format=json},
113
+ to_json)
114
+ end
115
+
116
+ def exists?
117
+ manage_connection.head(%Q{/manage/v2/databases/#{database_name}}).code.to_i == 200
118
+ end
119
+
120
+ def stale?
121
+ response = manage_connection.get(%Q{/manage/v2/databases/#{database_name}/properties?format=json})
122
+ raise Exception.new("Invalid response: #{response.code.to_i}: #{response.body}") if (response.code.to_i != 200)
123
+
124
+ props = JSON.parse(response.body)
125
+
126
+ INDEX_KEYS.each do |key|
127
+ if props[key]
128
+ local = @options[key].uniq.sort
129
+ remote = props[key].map { |json| MarkLogic::DatabaseSettings::Index.from_json(key, json) }.uniq.sort
130
+ unless local == remote
131
+ logger.debug "#{database_name}: #{local} != #{remote}"
132
+ return true
133
+ end
134
+ elsif @options.has_key?(key) and @options[key] != []
135
+ logger.debug "#{database_name}: #{key} is not on the remote end"
136
+ return true
137
+ end
138
+ end
139
+
140
+ return false
141
+ end
142
+
143
+ def drop
144
+ r = manage_connection.delete(%Q{/manage/v2/databases/#{database_name}?format=json})
145
+ end
146
+
147
+ def to_json
148
+ json = {}
149
+ @options.each do |k, v|
150
+ if v.kind_of?(Array)
151
+ value = v.map { |item| item.to_json }
152
+ else
153
+ value = v
154
+ end
155
+ json[k] = value
156
+ end
157
+ # puts json
158
+ json
159
+ end
160
+
161
+ def update
162
+ url = %Q{/manage/v2/databases/#{database_name}/properties?format=json}
163
+ r = manage_connection.put(url, JSON.generate(to_json))
164
+ end
165
+
166
+ def reset_indexes
167
+ INDEX_KEYS.each do |key|
168
+ @options[key] = []
169
+ end
170
+ end
171
+
172
+ def add_index(index_type, index)
173
+ @options[index_type] = [] unless @options[index_type]
174
+ @options[index_type] << index
175
+ @options[index_type].uniq! { |ii| ii.key }
176
+ application.add_index(index) if application
177
+ end
178
+
179
+ def range_index(name)
180
+ @options["range-element-index"].each do |index|
181
+ return index if index.localname == name
182
+ end
183
+ end
184
+
185
+ def has_range_index?(name)
186
+ @options["range-element-index"].each do |index|
187
+ return true if index.localname == name
188
+ end
189
+
190
+ return false
191
+ end
192
+
193
+ def collection(name)
194
+ MarkLogic::Collection.new(name, self)
195
+ end
196
+
197
+ def clear
198
+ r = connection.delete(%Q{/v1/search})
199
+ end
200
+
201
+ def collections()
202
+ return connection.run_query('cts:collections()', "xquery").body || []
203
+ end
204
+ end
205
+ end