grel 0.0.1 → 0.0.2

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 (4) hide show
  1. data/lib/grel.rb +85 -0
  2. data/lib/grel/base.rb +373 -0
  3. data/lib/grel/ql.rb +763 -0
  4. metadata +5 -2
data/lib/grel.rb ADDED
@@ -0,0 +1,85 @@
1
+ require 'stardog'
2
+ require 'time'
3
+ require 'uri'
4
+ require 'securerandom'
5
+ require 'debugger'
6
+
7
+ class Array
8
+ def triples_id
9
+ self.first.first
10
+ end
11
+ end
12
+
13
+ module GRel
14
+
15
+ class ValidationError < Stardog::ICVException
16
+ attr_accessor :icv_exception
17
+ def initialize(msg, exception)
18
+ super(msg)
19
+ @icv_exception = exception
20
+ end
21
+ end
22
+
23
+ DEBUG = ENV["GREL_DEBUG"] || false
24
+
25
+ class Debugger
26
+ def self.debug(msg)
27
+ puts msg if DEBUG
28
+ end
29
+ end
30
+
31
+ NAMESPACE = "http://grel.org/vocabulary#"
32
+ ID_REGEX = /^\@id\((\w+)\)$/
33
+ NIL = "\"http://www.w3.org/1999/02/22-rdf-syntax-ns#nil\""
34
+ BNODE = "BNODE"
35
+
36
+ class NonNegativeInteger
37
+
38
+ def initialize(number)
39
+ @number = number
40
+ end
41
+
42
+ def method_missing(name, *args, &blk)
43
+ ret = @number.send(name, *args, &blk)
44
+ ret.is_a?(Numeric) ? MyNum.new(ret) : ret
45
+ end
46
+
47
+ def to_s
48
+ "\"#{@number}\"^^<http://www.w3.org/2001/XMLSchema#nonNegativeInteger>"
49
+ end
50
+
51
+ end
52
+
53
+ class BlankId
54
+
55
+ attr_reader :blank_id
56
+
57
+ def initialize
58
+ @blank_id = BlankId.next_id
59
+ end
60
+
61
+ def self.next_id
62
+ next_id = (@counter ||= 0)
63
+ @counter += 1
64
+ next_id
65
+ end
66
+
67
+ def to_s
68
+ "_:#{@blank_id}"
69
+ end
70
+ end
71
+
72
+ def graph(name='http://localhost:5822/',options = {})
73
+ options[:user] ||= "admin"
74
+ options[:password] ||= "admin"
75
+ options[:validate] ||= false
76
+ g = Base.new(name, options)
77
+ g.with_db(options[:db]) if(options[:db])
78
+ g
79
+ end
80
+
81
+ end # end of module GRel
82
+
83
+ # remaining modules
84
+ require File.join(File.dirname(__FILE__), "grel", "ql")
85
+ require File.join(File.dirname(__FILE__), "grel", "base")
data/lib/grel/base.rb ADDED
@@ -0,0 +1,373 @@
1
+ module GRel
2
+
3
+ class Base
4
+
5
+ include Stardog
6
+
7
+ attr_accessor :stardog, :last_query_context
8
+ attr_reader :db_name, :schema_graph
9
+
10
+ def initialize(endpoint, options)
11
+ @options = options
12
+ @endpoint = endpoint
13
+ @stardog = Stardog::stardog(endpoint,options)
14
+ @validations = options[:validate] || false
15
+ @dbs = @stardog.list_dbs.body["databases"]
16
+ @reasoning = false
17
+ self
18
+ end
19
+
20
+ def with_reasoning(reasoning="QL")
21
+ @reasoning = true
22
+ @stardog = Stardog::stardog(@endpoint,@options.merge(:reasoning => reasoning))
23
+ @stardog.offline_db(@db_name)
24
+ @stardog.set_db_options(@db_name, "icv.reasoning.type" => reasoning)
25
+ @stardog.online_db(@db_name, 'WAIT')
26
+ self
27
+ end
28
+
29
+ def without_reasoning
30
+ @reasoning = false
31
+ @stardog = Stardog::stardog(@endpoint,@options)
32
+ self
33
+ end
34
+
35
+ def with_db(db_name)
36
+ ensure_db(db_name) do
37
+ old_db_name = @db_name
38
+ @db_name = db_name
39
+ @schema_graph = "#{db_name}:schema"
40
+ if block_given?
41
+ yield
42
+ @db_name = old_db_name
43
+ end
44
+ end
45
+ self
46
+ end
47
+
48
+
49
+ def store(data, db_name=nil)
50
+ if(db_name)
51
+ with_db(db_name) do
52
+ store(data)
53
+ end
54
+ else
55
+ GRel::Debugger.debug "STORING"
56
+ GRel::Debugger.debug QL.to_turtle(data)
57
+ GRel::Debugger.debug "IN"
58
+ GRel::Debugger.debug @db_name
59
+ @stardog.add(@db_name, QL.to_turtle(data), nil, "text/turtle")
60
+ end
61
+ self
62
+ rescue Stardog::ICVException => ex
63
+ raise ValidationError.new("Error storing objects in the graph. A Validation has failed.", ex)
64
+ end
65
+
66
+
67
+ def where(query)
68
+ @last_query_context = QL::QueryContext.new(self)
69
+ @last_query_context.register_query(query)
70
+ @last_query_context = QL.to_query(query, @last_query_context)
71
+ self
72
+ end
73
+
74
+ def union(query)
75
+ union_context = QL::QueryContext.new(self)
76
+ union_context.register_query(query)
77
+ union_context = QL.to_query(query, union_context)
78
+
79
+ @last_query_context.union(union_context)
80
+ self
81
+ end
82
+
83
+ def limit(limit)
84
+ @last_query_context.limit = limit
85
+ self
86
+ end
87
+
88
+ def offset(offset)
89
+ @last_query_context.offset = offset
90
+ self
91
+ end
92
+
93
+ def order(order)
94
+ @last_query_context.order = order
95
+ self
96
+ end
97
+
98
+ def run
99
+ @last_query_context.run
100
+ end
101
+
102
+ def define(*args)
103
+ unless(args.length == 3 && !args.first.is_a?(Array))
104
+ args = args.inject([]) {|a,i| a += i; a }
105
+ end
106
+
107
+ args = parse_schema_axioms(args)
108
+
109
+ triples = QL.to_turtle(args, true)
110
+ GRel::Debugger.debug "STORING IN SCHEMA #{@schema_graph}"
111
+ GRel::Debugger.debug triples
112
+ GRel::Debugger.debug "IN"
113
+ GRel::Debugger.debug @db_name
114
+ @stardog.add(@db_name, triples, @schema_graph, "text/turtle")
115
+ self
116
+ end
117
+
118
+ def retract_definition(*args)
119
+ unless(args.length == 3 && !args.first.is_a?(Array))
120
+ args = args.inject([]) {|a,i| a += i }
121
+ end
122
+ additional_triples = []
123
+
124
+ triples = QL.to_turtle(args + additional_triples, true)
125
+ GRel::Debugger.debug "REMOVING FROM SCHEMA #{@schema_graph}"
126
+ GRel::Debugger.debug triples
127
+ GRel::Debugger.debug "IN"
128
+ GRel::Debugger.debug @db_name
129
+ @stardog.remove(@db_name, triples, @schema_graph, "text/turtle")
130
+ self
131
+ end
132
+
133
+ def validate(*args)
134
+ unless(args.detect{|e| !e.is_a?(Array)})
135
+ args = args.inject([]) {|a,i| a += i; a }
136
+ end
137
+
138
+ args = parse_schema_axioms(args)
139
+ additional_triples = []
140
+ found = args.each_slice(3).detect{|(s,p,o)| p == :@range && o.is_a?(Class)}
141
+ if(found)
142
+ additional_triples += [found.first, :@type, :"<http://www.w3.org/2002/07/owl#DatatypeProperty>"]
143
+ end
144
+
145
+
146
+ triples = QL.to_turtle(args + additional_triples, true)
147
+ GRel::Debugger.debug "STORING IN VALIDATIONS #{@schema_graph}"
148
+ GRel::Debugger.debug triples
149
+ GRel::Debugger.debug "IN"
150
+ GRel::Debugger.debug @db_name
151
+ @stardog.add_icv(@db_name, triples, "text/turtle")
152
+ self
153
+ end
154
+
155
+ def retract_validation(*args)
156
+ unless(args.length == 3 && !args.first.is_a?(Array))
157
+ args = args.inject([]) {|a,i| a += i }
158
+ end
159
+ triples = QL.to_turtle(args, true)
160
+ GRel::Debugger.debug "REMOVING FROM SCHEMA #{@schema_graph}"
161
+ GRel::Debugger.debug triples
162
+ GRel::Debugger.debug "IN"
163
+ GRel::Debugger.debug @db_name
164
+ @stardog.remove_icv(@db_name, triples, "text/turtle")
165
+ self
166
+ end
167
+
168
+ def remove(data = nil, options = {})
169
+ if data
170
+ GRel::Debugger.debug "REMMOVING"
171
+ GRel::Debugger.debug QL.to_turtle(data)
172
+ GRel::Debugger.debug "IN"
173
+ GRel::Debugger.debug @db_name
174
+ @stardog.remove(@db_name, QL.to_turtle(data), nil, "text/turtle")
175
+ else
176
+ args = {:describe => true}
177
+ args = {:accept => "application/rdf+xml"}
178
+
179
+ sparql = @last_query_context.to_sparql_describe
180
+ triples = @stardog.query(@db_name,sparql, args).body
181
+
182
+ @stardog.remove(@db_name, triples, nil, "application/rdf+xml")
183
+ end
184
+ self
185
+ end
186
+
187
+ def all(options = {})
188
+ unlinked = options[:unlinked] || false
189
+
190
+ results = run
191
+ nodes = QL.from_bindings_to_nodes(results, @last_query_context, :unlinked => unlinked)
192
+ nodes
193
+ #sets = @last_query_context.query_keys
194
+ #nodes.select do |node|
195
+ # valid = false
196
+ # c = 0
197
+ # while(!valid && c<sets.length)
198
+ # sets_keys, sets_query = sets[c]
199
+ # valid = (sets_keys.empty?) || sets_keys.inject(true) do |ac,k|
200
+ # value = nil
201
+ # if (sets_query[k].is_a?(Hash) || (sets_query[k].is_a?(Symbol)))
202
+ # value = ac && node[k]
203
+ # end
204
+ # if(value.nil? && @reasoning == true)
205
+ # value = ac && node.values.include?(sets_query[k])
206
+ # end
207
+ # if (value.nil? && sets_query[k].is_a?(String) && sets_query[k].index("@id("))
208
+ # value = ac && node[k]
209
+ # end
210
+ # if(value.nil?)
211
+ # ac && node[k] == sets_query[k]
212
+ # else
213
+ # value
214
+ # end
215
+ # end
216
+ # c += 1
217
+ # end
218
+ # valid
219
+ #end
220
+ end
221
+
222
+ def tuples
223
+ results = run_tuples(@last_query_context.to_sparql_select)
224
+ results["results"]["bindings"].map do |h|
225
+ h.keys.each do |k|
226
+ h[k.to_sym] = QL.from_tuple_binding(h[k])
227
+ h.delete(k)
228
+ end
229
+ h
230
+ end
231
+ end
232
+
233
+ def first(options = {})
234
+ all(options).first
235
+ end
236
+
237
+ def query(query, options = {})
238
+ GRel::Debugger.debug "QUERYING DESCRIBE..."
239
+ GRel::Debugger.debug query
240
+ GRel::Debugger.debug "** LIMIT #{@last_query_context.limit}" if @last_query_context.limit
241
+ GRel::Debugger.debug "** OFFSET #{@last_query_context.offset}" if @last_query_context.offset
242
+ GRel::Debugger.debug "----------------------"
243
+ args = {:describe => true}
244
+ args[:accept] = options[:accept] if options[:accept]
245
+ args[:offset] = @last_query_context.offset if @last_query_context.offset
246
+ args[:limit] = @last_query_context.limit if @last_query_context.limit
247
+ @stardog.query(@db_name,query, args).body
248
+ end
249
+
250
+ def run_tuples(query, options = {})
251
+ GRel::Debugger.debug "QUERYING SELECT..."
252
+ GRel::Debugger.debug query
253
+ GRel::Debugger.debug "** LIMIT #{@last_query_context.limit}" if @last_query_context.limit
254
+ GRel::Debugger.debug "** OFFSET #{@last_query_context.offset}" if @last_query_context.offset
255
+ GRel::Debugger.debug "----------------------"
256
+ args = {}
257
+ args[:accept] = options[:accept] if options[:accept]
258
+ args[:offset] = @last_query_context.offset if @last_query_context.offset
259
+ args[:limit] = @last_query_context.limit if @last_query_context.limit
260
+ @stardog.query(@db_name,query, args).body
261
+ end
262
+
263
+ def with_validations(state = true)
264
+ @validations = state
265
+ @stardog.offline_db(@db_name)
266
+ @stardog.set_db_options(@db_name, "icv.enabled" => @validations)
267
+ @stardog.online_db(@db_name, 'WAIT')
268
+
269
+ self
270
+ end
271
+
272
+ def without_validations
273
+ with_validations(false)
274
+ end
275
+
276
+ private
277
+
278
+ def ensure_db(db_name)
279
+ unless(@dbs.include?(db_name))
280
+ @stardog.create_db(db_name, :options => { "reasoning.schema.graphs" => "#{db_name}:schema" })
281
+ @stardog.with_validations(@validations) if @validations == true
282
+ @dbs << db_name
283
+ end
284
+ yield if block_given?
285
+ end
286
+
287
+ def parse_schema_axioms(args)
288
+ unfolded = []
289
+ args.each_slice(3) do |(s,p,o)|
290
+ if(p == :@range && o.is_a?(Class))
291
+ unfolded += [s, :@type, :"<http://www.w3.org/2002/07/owl#DatatypeProperty>"]
292
+ elsif(p == :@range)
293
+ unfolded += [s, :@type, :"<http://www.w3.org/2002/07/owl#ObjectProperty>"]
294
+ end
295
+
296
+ if(p == :@some)
297
+ restriction = BlankId.new
298
+ unfolded += [s, :@subclass, restriction]
299
+ unfolded += [restriction, :@type, :"<http://www.w3.org/2002/07/owl#Restriction>"]
300
+ unfolded += [restriction, :"<http://www.w3.org/2002/07/owl#onProperty>", o.first]
301
+ unfolded += [restriction, :@some,o.last]
302
+ elsif(p == :@all)
303
+ restriction = BlankId.new
304
+ unfolded += [s, :@subclass, restriction]
305
+ unfolded += [restriction, :@type, :"<http://www.w3.org/2002/07/owl#Restriction>"]
306
+ unfolded += [restriction, :"<http://www.w3.org/2002/07/owl#onProperty>", o.first]
307
+ unfolded += [restriction, :@all,o.last]
308
+ elsif(p == :@cardinality)
309
+ property = o[:property]
310
+ klass = o[:class]
311
+ exact = o[:exact]
312
+ min = o[:min]
313
+ max = o[:max]
314
+ if klass.nil?
315
+ if(exact)
316
+ restriction = BlankId.new
317
+ unfolded += [s, :@subclass, restriction]
318
+ unfolded += [restriction, :@type, :"<http://www.w3.org/2002/07/owl#Restriction>"]
319
+ unfolded += [restriction, :"<http://www.w3.org/2002/07/owl#onProperty>", property]
320
+ unfolded += [restriction, :"<http://www.w3.org/2002/07/owl#cardinality>", NonNegativeInteger.new(exact)]
321
+ else
322
+ if(min)
323
+ restriction = BlankId.new
324
+ unfolded += [s, :@subclass, restriction]
325
+ unfolded += [restriction, :@type, :"<http://www.w3.org/2002/07/owl#Restriction>"]
326
+ unfolded += [restriction, :"<http://www.w3.org/2002/07/owl#onProperty>", property]
327
+ unfolded += [restriction, :"<http://www.w3.org/2002/07/owl#minCardinality>", NonNegativeInteger.new(min)]
328
+ end
329
+ if(max)
330
+ restriction = BlankId.new
331
+ unfolded += [s, :@subclass, restriction]
332
+ unfolded += [restriction, :@type, :"<http://www.w3.org/2002/07/owl#Restriction>"]
333
+ unfolded += [restriction, :"<http://www.w3.org/2002/07/owl#onProperty>", property]
334
+ unfolded += [restriction, :"<http://www.w3.org/2002/07/owl#maxCardinality>", NonNegativeInteger.new(max)]
335
+ end
336
+ end
337
+ else
338
+ if(exact)
339
+ restriction = BlankId.new
340
+ unfolded += [s, :@subclass, restriction]
341
+ unfolded += [restriction, :@type, :"<http://www.w3.org/2002/07/owl#Restriction>"]
342
+ unfolded += [restriction, :"<http://www.w3.org/2002/07/owl#onProperty>", property]
343
+ unfolded += [restriction, :"<http://www.w3.org/2002/07/owl#qualifiedCardinality>", NonNegativeInteger.new(exact)]
344
+ unfolded += [restriction, :"<http://www.w3.org/2002/07/owl#onClass>", klass]
345
+ else
346
+ if(min)
347
+ restriction = BlankId.new
348
+ unfolded += [s, :@subclass, restriction]
349
+ unfolded += [restriction, :@type, :"<http://www.w3.org/2002/07/owl#Restriction>"]
350
+ unfolded += [restriction, :"<http://www.w3.org/2002/07/owl#onProperty>", property]
351
+ unfolded += [restriction, :"<http://www.w3.org/2002/07/owl#minQualifiedCardinality>", NonNegativeInteger.new(min)]
352
+ unfolded += [restriction, :"<http://www.w3.org/2002/07/owl#onClass>", klass]
353
+ end
354
+ if(max)
355
+ restriction = BlankId.new
356
+ unfolded += [s, :@subclass, restriction]
357
+ unfolded += [restriction, :@type, :"<http://www.w3.org/2002/07/owl#Restriction>"]
358
+ unfolded += [restriction, :"<http://www.w3.org/2002/07/owl#onProperty>", property]
359
+ unfolded += [restriction, :"<http://www.w3.org/2002/07/owl#maxQualifiedCardinality>", NonNegativeInteger.new(max)]
360
+ unfolded += [restriction, :"<http://www.w3.org/2002/07/owl#onClass>", klass]
361
+ end
362
+ end
363
+ end
364
+ else
365
+ unfolded += [s,p,o]
366
+ end
367
+ end
368
+
369
+ unfolded
370
+ end
371
+
372
+ end # end of Base class
373
+ end # end of Grel module
data/lib/grel/ql.rb ADDED
@@ -0,0 +1,763 @@
1
+ module GRel
2
+ module QL
3
+ def self.to_id(obj)
4
+ if(obj =~ ID_REGEX)
5
+ "<http://grel.org/ids/id/#{URI.encode(ID_REGEX.match(obj)[1])}>"
6
+ else
7
+ "<http://grel.org/ids/#{URI.encode(obj)}>"
8
+ end
9
+ end
10
+
11
+ def self.to_turtle(obj, schema=false)
12
+ data = "@prefix : <http://grel.org/vocabulary#> . @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . "
13
+ data = data + "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . " if schema
14
+
15
+ data + QL.to_triples(obj).map{|t| t.map(&:to_s).join(" ") }.join(" .\n ") + " ."
16
+ end
17
+
18
+ def self.to_triples(obj)
19
+ if(obj.is_a?(BlankId))
20
+ obj.to_s
21
+ elsif(obj.is_a?(Symbol))
22
+ if(obj == :@type)
23
+ "rdf:type"
24
+ elsif(obj == :@subclass)
25
+ "rdfs:subClassOf"
26
+ elsif(obj == :@subproperty)
27
+ "rdfs:subPropertyOf"
28
+ elsif(obj == :@domain)
29
+ "rdfs:domain"
30
+ elsif(obj == :@range)
31
+ "rdfs:range"
32
+ elsif(obj == :"<http://www.w3.org/2002/07/owl#onClass>")
33
+ "<http://www.w3.org/2002/07/owl#onClass>"
34
+ elsif(obj == :"<http://www.w3.org/2002/07/owl#qualifiedCardinality>")
35
+ "<http://www.w3.org/2002/07/owl#qualifiedCardinality>"
36
+ elsif(obj == :"<http://www.w3.org/2002/07/owl#minCardinality>")
37
+ "<http://www.w3.org/2002/07/owl#minCardinality>"
38
+ elsif(obj == :"<http://www.w3.org/2002/07/owl#maxCardinality>")
39
+ "<http://www.w3.org/2002/07/owl#maxCardinality>"
40
+ elsif(obj == :"<http://www.w3.org/2002/07/owl#minQualifiedCardinality>")
41
+ "<http://www.w3.org/2002/07/owl#minQualifiedCardinality>"
42
+ elsif(obj == :"<http://www.w3.org/2002/07/owl#maxQualifiedCardinality>")
43
+ "<http://www.w3.org/2002/07/owl#maxQualifiedCardinality>"
44
+ elsif(obj == :"<http://www.w3.org/2002/07/owl#cardinality>")
45
+ "<http://www.w3.org/2002/07/owl#cardinality>"
46
+ elsif(obj == :@some)
47
+ "<http://www.w3.org/2002/07/owl#someValuesFrom>"
48
+ elsif(obj == :@all)
49
+ "<http://www.w3.org/2002/07/owl#allValuesFrom>"
50
+ elsif(obj == :"<http://www.w3.org/2002/07/owl#Restriction>")
51
+ "<http://www.w3.org/2002/07/owl#Restriction>"
52
+ elsif(obj == :"<http://www.w3.org/2002/07/owl#onProperty>")
53
+ "<http://www.w3.org/2002/07/owl#onProperty>"
54
+ elsif(obj == :"<http://www.w3.org/2002/07/owl#DatatypeProperty>")
55
+ "<http://www.w3.org/2002/07/owl#DatatypeProperty>"
56
+ elsif(obj == :"<http://www.w3.org/2002/07/owl#ObjectProperty>")
57
+ "<http://www.w3.org/2002/07/owl#ObjectProperty>"
58
+ else
59
+ ":#{obj}"
60
+ end
61
+ elsif(obj.is_a?(String))
62
+ if(obj =~ ID_REGEX)
63
+ QL.to_id(obj)
64
+ else
65
+ "\"#{obj}\""
66
+ end
67
+ elsif(obj == nil)
68
+ NIL
69
+ elsif(obj == Float)
70
+ "<http://www.w3.org/2001/XMLSchema#float>"
71
+ elsif(obj == Numeric || obj == Fixnum || obj == "Bignum")
72
+ "<http://www.w3.org/2001/XMLSchema#integer>"
73
+ elsif(obj == Time || obj == Date || obj == DateTime)
74
+ "<http://www.w3.org/2001/XMLSchema#dateTime>"
75
+ elsif(obj == TrueClass || obj == FalseClass)
76
+ "<http://www.w3.org/2001/XMLSchema#boolean>"
77
+ elsif(obj.is_a?(NonNegativeInteger))
78
+ obj.to_s
79
+ elsif(obj == true || obj == false)
80
+ "\"#{obj}\"^^<http://www.w3.org/2001/XMLSchema#boolean>"
81
+ elsif(obj.is_a?(Float))
82
+ "\"#{obj}\"^^<http://www.w3.org/2001/XMLSchema#float>"
83
+ elsif(obj.is_a?(Numeric))
84
+ "\"#{obj}\"^^<http://www.w3.org/2001/XMLSchema#integer>"
85
+ elsif(obj.is_a?(Time))
86
+ "\"#{obj.iso8601}\"^^<http://www.w3.org/2001/XMLSchema#dateTime>"
87
+ elsif(obj.is_a?(Date))
88
+ "\"#{Time.new(obj.to_s).iso8601}\"^^<http://www.w3.org/2001/XMLSchema#dateTime>"
89
+ elsif(obj.is_a?(Array)) # top level array, not array property in a hash
90
+ if(obj.detect{|e| e.is_a?(Hash) || e.respond_to?(:to_triples) })
91
+ obj.map{|e| QL.to_triples(e) }.inject([]){|a,i| a += i}
92
+ else
93
+ obj.each_slice(3).map do |s|
94
+ s.map{|e| QL.to_triples(e) }
95
+ end
96
+ end
97
+ elsif(obj.is_a?(Hash))
98
+ # no blank nodes
99
+ obj[:@id] = "@id(#{SecureRandom.hex})" if obj[:@id].nil?
100
+ # normalising id values
101
+ obj[:@id] = "@id(#{obj[:@id]})" if obj[:@id].index("@id(").nil?
102
+
103
+ acum = []
104
+ triples_acum = []
105
+ triples_nested = []
106
+ id = nil
107
+ obj.each_pair do |k,v|
108
+ p = QL.to_triples(k)
109
+ if(v.is_a?(Hash))
110
+ next_triples = QL.to_triples(v)
111
+ triples_acum += next_triples
112
+ v = next_triples.triples_id
113
+ acum << [p,v] if v && k != :@id
114
+ elsif(v.is_a?(Array)) # array as a property in a hash
115
+ v.map{|o| QL.to_triples(o) }.each do |o|
116
+ if(o.is_a?(Array) && o.length > 0)
117
+ acum << [p, o[0][0]]
118
+ triples_nested += o
119
+ else
120
+ acum << [p, o]
121
+ end
122
+ end
123
+ else
124
+ if(k == :@id)
125
+ id = QL.to_triples(v)
126
+ else
127
+ v = QL.to_triples(v)
128
+ end
129
+ acum << [p,v] if v && k != :@id
130
+ end
131
+ end
132
+
133
+ id = id || BlankId.new
134
+
135
+ triples_acum + acum.map{|(p,o)| [id, p, o] } + triples_nested
136
+ else
137
+ if(obj.respond_to?(:to_triples))
138
+ obj.to_triples
139
+ else
140
+ "\"#{obj}\""
141
+ end
142
+ end
143
+ end # end of to_triples
144
+
145
+ class QueryContext
146
+
147
+ TUPLE = "TUPLE"
148
+ NODE = "NODE"
149
+
150
+ attr_reader :last_registered_subject, :nodes, :projection, :limit, :offset, :order, :orig_query
151
+ attr_accessor :triples, :optional_triples, :optional, :unions
152
+
153
+ def initialize(graph=nil)
154
+ @id_counter = -1
155
+ @filters_counter = -1
156
+ @triples = []
157
+ @optional_triples = []
158
+ @optional_bgps = []
159
+ @projection = {}
160
+ @nodes = {}
161
+ @graph = graph
162
+ @optional = false
163
+ @last_registered_subject = []
164
+ @unions = []
165
+ @limit = nil
166
+ @offset = nil
167
+ @orig_query = nil
168
+ @query_keys = []
169
+ end
170
+
171
+ def register_query(query)
172
+ @orig_query = query
173
+ query.each_pair do |k,v|
174
+ @query_keys << k if(k != :@id && k.to_s.index("$inv_").nil?)
175
+ end
176
+ self
177
+ end
178
+
179
+ def query_keys
180
+ sets = [[@query_keys,@orig_query]]
181
+ unions.each do |c|
182
+ sets += c.query_keys
183
+ end
184
+ sets
185
+ end
186
+
187
+ def optional=(value)
188
+ if(value == true)
189
+ @optional = true
190
+ else
191
+ @optional = false
192
+ @optional_bgps << @optional_triples
193
+ @optional_triples = []
194
+ end
195
+ end
196
+
197
+ def append(triples)
198
+ if(@optional)
199
+ @optional_triples += triples
200
+ else
201
+ @triples += triples
202
+ end
203
+ end
204
+
205
+ def next_node_id
206
+ @id_counter += 1
207
+ @id_counter
208
+ end
209
+
210
+ def register_node(id,node_id,inverse=false)
211
+ @filters_counter = -1
212
+ subject = id
213
+ predicate = "?P_mg_#{node_id}"
214
+ object = "?O_mg_#{node_id}"
215
+ if(id.nil?)
216
+ subject = "?S_mg_#{node_id}"
217
+ @projection[subject] = true if(!inverse)
218
+ else
219
+ @projection[id] = true if(!inverse)
220
+ end
221
+
222
+ @last_registered_subject << subject
223
+ # @projection[predicate] = true
224
+ # @projection[object] = true
225
+ @nodes[node_id] = subject
226
+
227
+ [subject, predicate, object]
228
+ end
229
+
230
+ def last_registered_subject
231
+ @last_registered_subject.pop
232
+ end
233
+
234
+ def node_to_optional(node_id) #, last_registered_subject)
235
+ @projection.delete(node_id)
236
+ @nodes.delete(node_id)
237
+ # @last_registered_subject << last_registered_subject
238
+ end
239
+
240
+ def fresh_filter_predicate
241
+ @filters_counter+=1
242
+ "?P_#{@id_counter}_#{@filters_counter}"
243
+ end
244
+
245
+ def to_sparql_describe(preamble=true)
246
+
247
+ bgps = @triples.map{|t|
248
+ if(t.is_a?(Filter))
249
+ t.acum
250
+ else
251
+ t.join(' ')
252
+ end
253
+ }.join(" . ")
254
+
255
+ optional_bgps = @optional_bgps.map{|optional_triples|
256
+ "OPTIONAL { "+ optional_triples.map { |t|
257
+ if(t.is_a?(Filter))
258
+ t.acum
259
+ else
260
+ t.join(' ')
261
+ end
262
+ }.join(" . ") + " }"
263
+ }.join(" ")
264
+
265
+ main_bgp = "#{bgps}"
266
+ unless(@optional_bgps.empty?)
267
+ main_bgp += " #{optional_bgps}"
268
+ end
269
+
270
+ query = if(preamble)
271
+ query = "PREFIX : <http://grel.org/vocabulary#> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX rdfs: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>"
272
+ query = query + " PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> PREFIX fn: <http://www.w3.org/2005/xpath-functions#> "
273
+
274
+ if(@unions.length>0)
275
+ union_bgps = @unions.map{|u| u.to_sparql_describe(false) }.join(" UNION ")
276
+ union_projections = @unions.map{|u| u.projection.keys }.flatten.uniq
277
+ all_subjects = (@projection.keys + union_projections).uniq
278
+ query += "DESCRIBE #{all_subjects.join(' ')} WHERE { { #{main_bgp} } UNION #{union_bgps} }"
279
+ else
280
+ query += "DESCRIBE #{@projection.keys.join(' ')} WHERE { #{main_bgp} }"
281
+ end
282
+ else
283
+ "{ #{main_bgp} }"
284
+ end
285
+ end
286
+
287
+ def to_sparql_select(preamble=true)
288
+
289
+ projection = {}
290
+
291
+ bgps = @triples.map{ |t|
292
+ s,p,o = t
293
+ if(s.to_s.index("?X_") == 0 && p.to_s.index("?X_") == 0 && o.to_s.index("?X_") == 0)
294
+ elsif(s.to_s.index("?S_mg") == 0 && p.to_s.index("?P_mg") == 0 && o.to_s.index("?O_mg") == 0)
295
+ nil
296
+ else
297
+ projection[s] = true if((s.to_s.index("?") == 0) && s.to_s.index("?X_").nil? && s.index("_mg_").nil?)
298
+ projection[p] = true if((p.to_s.index("?") == 0) && p.to_s.index("?X_").nil? && p.index("_mg_").nil?)
299
+ projection[o] = true if((o.to_s.index("?") == 0) && o.to_s.index("?X_").nil? && o.index("_mg_").nil?)
300
+ if(t.is_a?(Filter))
301
+ t.acum
302
+ else
303
+ t.join(' ')
304
+ end
305
+ end
306
+ }.compact.join(" . ")
307
+
308
+ optional_bgps = @optional_bgps.map{|optional_triples|
309
+ "OPTIONAL { "+ optional_triples.map { |t|
310
+ s,p,o = t
311
+ if(s.to_s.index("?") == 0 && p.to_s.index("?") == 0 && o.to_s.index("?") == 0)
312
+ nil
313
+ else
314
+ projection[s] = true if((s.to_s.index("?") == 0) && s.to_s.index("?X_").nil?)
315
+ projection[p] = true if((p.to_s.index("?") == 0) && p.to_s.index("?X_").nil?)
316
+ projection[o] = true if((o.to_s.index("?") == 0) && o.to_s.index("?X_").nil?)
317
+ if(t.is_a?(Filter))
318
+ t.acum
319
+ else
320
+ t.join(' ')
321
+ end
322
+ end
323
+ }.compact.join(" . ") + " }"
324
+ }.join(" ")
325
+
326
+ main_bgp = "#{bgps}"
327
+ unless(@optional_bgps.empty?)
328
+ main_bgp += " #{optional_bgps}"
329
+ end
330
+
331
+ query = if(preamble)
332
+ query = "PREFIX : <http://grel.org/vocabulary#> PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX rdfs: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>"
333
+ query = query + " PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> PREFIX fn: <http://www.w3.org/2005/xpath-functions#> "
334
+
335
+ if(@unions.length>0)
336
+ union_bgps = @unions.map{|u| u.to_sparql_describe(false) }.join(" UNION ")
337
+ all_subjects = projection.keys
338
+ query += "SELECT DISTINCT #{all_subjects.join(' ')} WHERE { { #{main_bgp} } UNION #{union_bgps} }"
339
+ else
340
+ all_subjects = projection.keys
341
+ query += "SELECT DISTINCT #{all_subjects.join(' ')} WHERE { #{main_bgp} }"
342
+ end
343
+ else
344
+ "{ #{main_bgp} }"
345
+ end
346
+ end
347
+
348
+ def limit=(limit)
349
+ @limit = limit
350
+ self
351
+ end
352
+
353
+ def offset=(offset)
354
+ @offset = offset
355
+ self
356
+ end
357
+
358
+ def union(other_context)
359
+ @unions << other_context
360
+ self
361
+ end
362
+
363
+ def run
364
+ @graph.query(to_sparql_describe)
365
+ end
366
+
367
+ def required_properties
368
+ props = []
369
+
370
+ end
371
+ end # end of QueryContext
372
+
373
+ class Filter
374
+ DEFINED_FILTERS = {
375
+ :$neq => true, :$eq => true, :$lt => true,
376
+ :$lteq => true, :$gt => true, :$gteq => true,
377
+ :$not => true, :$like => true,
378
+ :$or => true, :$and => true, :$in => true
379
+ }
380
+
381
+ def self.filter?(h)
382
+ h.keys.length == 1 && DEFINED_FILTERS[h.keys.first]
383
+ end
384
+
385
+ attr_reader :variable, :acum
386
+
387
+ def initialize(context)
388
+ @context = context
389
+ @acum = ""
390
+ @variable = context.fresh_filter_predicate
391
+ end
392
+
393
+ def parse(h)
394
+ parse_filter(h)
395
+ @acum = "FILTER("+ @acum + ")"
396
+ end
397
+
398
+ def parse_filter(h)
399
+ k = h.keys.first
400
+ v = h.values.first
401
+ self.send "generate_#{k.to_s.split("$").last}".to_sym, v
402
+ end
403
+
404
+ def generate_in(v)
405
+ generate_or v.map{|x| {:$eq => x} }
406
+ end
407
+
408
+ def generate_neq(v)
409
+ @acum = @acum + "(#{variable} != "
410
+ operator = if(v.is_a?(Hash))
411
+ parse_filter(v)
412
+ else
413
+ QL.to_query(v,@context)
414
+ end
415
+ @acum+="#{operator})"
416
+ end
417
+
418
+ def generate_eq(v)
419
+ @acum = @acum + "(#{variable} = "
420
+ operator = if(v.is_a?(Hash))
421
+ parse_filter(v)
422
+ else
423
+ QL.to_query(v,@context)
424
+ end
425
+ @acum+="#{operator})"
426
+ end
427
+
428
+ def generate_lt(v)
429
+ @acum = @acum + "(#{variable} < "
430
+ operator = if(v.is_a?(Hash))
431
+ parse_filter(v)
432
+ else
433
+ QL.to_query(v,@context)
434
+ end
435
+ @acum+="#{operator})"
436
+ end
437
+
438
+ def generate_lteq(v)
439
+ @acum = @acum + "(#{variable} <= "
440
+ operator = if(v.is_a?(Hash))
441
+ parse_filter(v)
442
+ else
443
+ QL.to_query(v,@context)
444
+ end
445
+ @acum+="#{operator})"
446
+ end
447
+
448
+ def generate_gt(v)
449
+ @acum = @acum + "(#{variable} > "
450
+ operator = if(v.is_a?(Hash))
451
+ parse_filter(v)
452
+ else
453
+ QL.to_query(v,@context)
454
+ end
455
+ @acum+="#{operator})"
456
+ end
457
+
458
+ def generate_gteq(v)
459
+ @acum = @acum + "(#{variable} <= "
460
+ operator = if(v.is_a?(Hash))
461
+ parse_filter(v)
462
+ else
463
+ QL.to_query(v,@context)
464
+ end
465
+ @acum+="#{operator})"
466
+ end
467
+
468
+ def generate_not(v)
469
+ @acum += "!("
470
+ if(v.is_a?(Hash))
471
+ parse_filter(v)
472
+ else
473
+ @acum += QL.to_query(v,@context)
474
+ end
475
+ @acum += ")"
476
+ end
477
+
478
+ def generate_like(v)
479
+ operator = if(v.is_a?(Regexp))
480
+ v.source
481
+ elsif(v.is_a?(String))
482
+ v
483
+ else
484
+ raise Exception.new("Only Regexes and Strings can be used with the $like operator")
485
+ end
486
+ @acum += "(regex(#{variable},\"#{operator}\",\"i\"))"
487
+ end
488
+
489
+ def generate_or(v)
490
+ if(v.is_a?(Array))
491
+ @acum += "("
492
+ v.each_with_index do |f,i|
493
+ parse_filter(f)
494
+ @acum += "||" if i<v.length-1
495
+ end
496
+ @acum += ")"
497
+ else
498
+ raise Exception.new("$or filter must accept an array of conditions")
499
+ end
500
+ end
501
+
502
+ def generate_and(v)
503
+ if(v.is_a?(Array))
504
+ @acum += "("
505
+ v.each_with_index do |f,i|
506
+ parse_filter(f)
507
+ @acum += "&&" if i<v.length-1
508
+ end
509
+ @acum += ")"
510
+ else
511
+ raise Exception.new("$or filter must accept an array of conditions")
512
+ end
513
+ end
514
+
515
+ end
516
+
517
+ class BGP
518
+
519
+ attr_reader :bgp_id, :context, :data
520
+
521
+ def initialize(data, context, inverse=false)
522
+ @data = data
523
+ @context = context
524
+ @bgp_id = nil
525
+ @inverse = inverse
526
+ end
527
+
528
+ def to_query
529
+ acum = []
530
+
531
+ node_id = @context.next_node_id
532
+ id = @data[:@id]
533
+ id = "@id(#{id})" if id && id.is_a?(String) && id.index("@id(").nil?
534
+ id = QL.to_query(id, context) if id
535
+ subject_var_id,pred_var_id, obj_var_id = @context.register_node(id,node_id,@inverse)
536
+
537
+ filters = []
538
+
539
+ @data.inject([]) do |ac,(k,v)|
540
+ # $optional can point to an array of conditions that must be handled separetedly
541
+ if(k.to_s == "$optional" && v.is_a?(Array))
542
+ v.each{ |c| ac << [k,c] }
543
+ else
544
+ ac << [k,v]
545
+ end
546
+ ac
547
+ end.each do |(k,v)|
548
+ # process each property in the query hash
549
+ inverse = false
550
+ optional = false
551
+ unless(k == :@id)
552
+ prop = if(k.to_s.index("$inv_"))
553
+ inverse = true
554
+ k.to_s
555
+ elsif(k.to_s == "$optional")
556
+ context.optional = true
557
+ optional = true
558
+ else
559
+ parse_property(k)
560
+ end
561
+ value = QL.to_query(v,context,inverse)
562
+ if(value.is_a?(Array))
563
+ context.append(value,false)
564
+ value = value.triples_id
565
+ elsif(value.is_a?(Filter))
566
+ filters << value
567
+ value = value.variable
568
+ elsif(value == context) # It was a nested hash, retrieve the subject as object value
569
+ if(optional)
570
+ subject_to_replace = value.last_registered_subject
571
+ context.node_to_optional(subject_to_replace)#, subject_var_id)
572
+ context.optional_triples = context.optional_triples.map do |(s,p,o)|
573
+ s = (s == subject_to_replace ? subject_var_id : s)
574
+ o = (o == subject_to_replace ? subject_var_id : o)
575
+ [s, p, o]
576
+ end
577
+ context.optional = false
578
+ else
579
+ value = context.last_registered_subject
580
+ end
581
+ end
582
+ acum << [prop,value] unless optional
583
+ end
584
+ end
585
+
586
+ bgp_id = subject_var_id
587
+ acum = acum.map do |(p,o)|
588
+ if(p.index("$inv_"))
589
+ [o,QL.to_query(p.to_sym,@context),subject_var_id]
590
+ else
591
+ [subject_var_id,p,o]
592
+ end
593
+ end
594
+ acum << [subject_var_id, pred_var_id, obj_var_id] if acum.empty?
595
+ acum += filters
596
+ acum
597
+ end
598
+
599
+ def parse_property(k)
600
+ QL.to_query(k, context)
601
+ end
602
+
603
+ end # end of BGP
604
+
605
+ def self.to_query(obj,context=QL::QueryContext.new, inverse=false)
606
+ if(obj.is_a?(Symbol))
607
+ if(obj == :@type)
608
+ "rdf:type"
609
+ elsif(obj.to_s.index("$inv_"))
610
+ ":#{obj.to_s.split("$inv_").last}"
611
+ elsif(obj.to_s == "_")
612
+ "?X_#{obj.to_s.split('_').last}_#{context.next_node_id}"
613
+ elsif(obj.to_s.index("_"))
614
+ "?#{obj.to_s.split("_").drop(1).join("_")}"
615
+ else
616
+ ":#{obj}"
617
+ end
618
+ elsif(obj.is_a?(String))
619
+ if(obj =~ ID_REGEX)
620
+ QL.to_id(obj)
621
+ else
622
+ "\"#{obj}\""
623
+ end
624
+ elsif(obj.is_a?(Float))
625
+ "\"#{obj}\"^^<http://www.w3.org/2001/XMLSchema#float>"
626
+ elsif(obj.is_a?(Numeric))
627
+ "\"#{obj}\"^^<http://www.w3.org/2001/XMLSchema#integer>"
628
+ elsif(obj.is_a?(Time))
629
+ "\"#{obj.iso8601}\"^^<http://www.w3.org/2001/XMLSchema#dateTime>"
630
+ elsif(obj.is_a?(Date))
631
+ "\"#{Time.new(obj.to_s).iso8601}\"^^<http://www.w3.org/2001/XMLSchema#dateTime>"
632
+ elsif(obj.is_a?(Array))
633
+ # TODO
634
+ elsif(obj.is_a?(Hash))
635
+ if(Filter.filter?(obj))
636
+ filter = Filter.new(context)
637
+ filter.parse(obj)
638
+ filter
639
+ else
640
+ bgp = BGP.new(obj,context,inverse)
641
+ context.append(bgp.to_query)
642
+ context
643
+ end
644
+ else
645
+ if(obj.respond_to?(:to_query))
646
+ obj.to_query(context)
647
+ else
648
+ "\"#{obj}\""
649
+ end
650
+ end
651
+ end # end of to_query
652
+
653
+ def self.from_tuple_binding(tuple_value)
654
+ if(tuple_value["type"] == "uri")
655
+ from_binding_to_id(tuple_value["value"])
656
+ elsif(tuple_value["type"] == "literal")
657
+ tuple_value["value"]
658
+ else
659
+ tuple_value["@type"] = tuple_value["datatype"]
660
+ tuple_value["@value"] = tuple_value["value"]
661
+ from_binding_value(tuple_value)
662
+ end
663
+ end
664
+
665
+ def self.from_binding_to_id(obj)
666
+ if(!obj.is_a?(String))
667
+ obj
668
+ elsif(obj == "http://www.w3.org/1999/02/22-rdf-syntax-ns#nil")
669
+ nil
670
+ elsif(obj.index("http://grel.org/ids/id/"))
671
+ val = URI.unescape(obj.split("http://grel.org/ids/id/").last)
672
+ "@id(#{val})"
673
+ elsif(obj.index("http://grel.org/ids/"))
674
+ URI.unescape(obj.split("http://grel.org/ids/").last)
675
+ elsif(obj.index("http://grel.org/vocabulary#"))
676
+ obj.split("http://grel.org/vocabulary#").last.to_sym
677
+ else
678
+ obj
679
+ end
680
+ end
681
+
682
+ def self.from_binding_value(obj)
683
+ if(obj.is_a?(Hash) && obj["@id"])
684
+ from_binding_hash(obj)
685
+ elsif(obj.is_a?(Hash) && obj["@type"])
686
+ if(obj["@type"] == "http://www.w3.org/2001/XMLSchema#dateTime")
687
+ Time.parse(obj["@value"])
688
+ elsif(obj["@type"] == "http://www.w3.org/2001/XMLSchema#integer")
689
+ obj["@value"].to_i
690
+ elsif(obj["@type"] == "http://www.w3.org/2001/XMLSchema#float")
691
+ obj["@value"].to_f
692
+ elsif(obj["@type"] == "http://www.w3.org/2001/XMLSchema#boolean")
693
+ (obj["@value"] == "true" ? true : false)
694
+ else
695
+ obj["@value"]
696
+ end
697
+ elsif(obj.is_a?(Array))
698
+ obj.map{|o| from_binding_value(o)}
699
+ else
700
+ from_binding_to_id(obj)
701
+ end
702
+ end # end of from_binding_value
703
+
704
+ def self.from_binding_hash(node)
705
+ node = node.raw_json if node.respond_to?(:raw_json)
706
+ node.delete("@context")
707
+ node = node.to_a.inject({}) do |ac, (p,v)|
708
+ p = p[1..-1].to_sym if(p.index(":") == 0)
709
+ p = p.to_sym if(p == "@id" || p == "@type")
710
+ v = from_binding_value(v)
711
+ ac[p] = v; ac
712
+ end
713
+ node
714
+ end
715
+
716
+ def self.from_bindings_to_nodes(bindings,context, options = {})
717
+ unlinked = options[:unlinked] || false
718
+ nodes = {}
719
+ uris = {}
720
+ json = bindings
721
+ json = [json] unless json.is_a?(Array)
722
+ json.each do|node|
723
+ node = from_binding_hash(node)
724
+ nodes[node[:@id]] = node
725
+ node.delete(:@id) unless node[:@id].index("@id(")
726
+ end
727
+
728
+ nodes.each do |(node_id,node)|
729
+ node.each_pair do |k,v|
730
+ # weird things happening with describe queries and arrays
731
+ if(v.is_a?(Array))
732
+ v = v.uniq
733
+ v = v.first if v.length ==1
734
+ node[k] = v
735
+ end
736
+
737
+ if(v.is_a?(Hash) && v[:@id] && nodes[v[:@id]])
738
+ node[k] = nodes[v[:@id]]
739
+ nodes.delete(v[:@id]) if unlinked
740
+ elsif(v.is_a?(Hash) && v[:@id])
741
+ node[k] = from_binding_to_id(v[:@id])
742
+ elsif(v.is_a?(Array))
743
+ node[k] = v.map do |o|
744
+ # recursive execution for each element in the array
745
+ if(o.is_a?(Hash) && o[:@id] && nodes[o[:@id]])
746
+ to_link = nodes[o[:@id]]
747
+ nodes.delete(o[:@id]) if unlinked
748
+ to_link
749
+ elsif(o.is_a?(Hash) && o[:@id])
750
+ from_binding_to_id(o[:@id])
751
+ else
752
+ o
753
+ end
754
+ end
755
+ end
756
+ end
757
+ end
758
+
759
+ nodes.values
760
+ end
761
+
762
+ end # end of class QL
763
+ end # end of GRel module
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -33,7 +33,10 @@ email:
33
33
  executables: []
34
34
  extensions: []
35
35
  extra_rdoc_files: []
36
- files: []
36
+ files:
37
+ - lib/grel/base.rb
38
+ - lib/grel/ql.rb
39
+ - lib/grel.rb
37
40
  homepage: https://github.com/antoniogarrote/grel
38
41
  licenses: []
39
42
  post_install_message: