marklogic 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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