grel 0.0.1 → 0.0.2

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