solis 0.97.0 → 0.98.0
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.
- checksums.yaml +4 -4
- data/lib/solis/query/result_transformer.rb +130 -0
- data/lib/solis/query/run.rb +60 -100
- data/lib/solis/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5b5f53e6ee613f19d1de7527908da768b55623f51f45bdc4ae31ab3ba338a5cc
|
|
4
|
+
data.tar.gz: d1dfade779a59ad0c36422a20bcc1b8b21fc7c74b75774e1b69be9488050c9ef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ec93a8f1b15c02a1b2b5b52e4f85d0d3058c2528ad73d33d15944e5a3c5609bbfe9e2e83e8ab15efc41c1e98350745903b089cf3902cd2157a3a2aaaeb0f5657
|
|
7
|
+
data.tar.gz: fd75bb60d80282d6c285b0bb004cdb466a0fa3d6ce2de9960732bb201e5d068c6c4256e7cf560dbf1b67b86371706659f562bc72bb149826285f22cefac6ba84
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
class Solis::Query::ResultTransformer
|
|
2
|
+
def initialize(model)
|
|
3
|
+
@model = model
|
|
4
|
+
@type_mappings = load_type_mappings
|
|
5
|
+
@cardinality_map = build_cardinality_map
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def transform(data)
|
|
9
|
+
items = data.key?('@graph') ? data['@graph'] : [data]
|
|
10
|
+
items.map { |item| transform_item(item) }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def transform_item(item)
|
|
16
|
+
clean_item = remove_json_ld_metadata(item)
|
|
17
|
+
cast_and_shape(clean_item)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def cast_and_shape(item)
|
|
21
|
+
item.each_with_object({}) do |(key, value), result|
|
|
22
|
+
value = cast_value(value)
|
|
23
|
+
result[key] = enforce_cardinality(key, value)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# def cast_and_shape(item)
|
|
28
|
+
# item.each_with_object({}) do |(key, value), result|
|
|
29
|
+
# value = cast_value(value)
|
|
30
|
+
# value = enforce_cardinality(key, value)
|
|
31
|
+
# value = transform_nested_entity(key, value)
|
|
32
|
+
# result[key] = value
|
|
33
|
+
# end
|
|
34
|
+
# end
|
|
35
|
+
|
|
36
|
+
def transform_nested_entity(property_key, value)
|
|
37
|
+
return value if @model.nil?
|
|
38
|
+
|
|
39
|
+
metadata = @cardinality_map[property_key]
|
|
40
|
+
return value if metadata.nil?
|
|
41
|
+
|
|
42
|
+
datatype = metadata[:datatype]
|
|
43
|
+
node = metadata[:node]
|
|
44
|
+
|
|
45
|
+
# Check if datatype points to another entity (has a node)
|
|
46
|
+
if node.is_a?(RDF::URI) && value.is_a?(Hash)
|
|
47
|
+
# Recursively transform nested entity if we have its model
|
|
48
|
+
# You'd need a way to resolve the node URI to the model class
|
|
49
|
+
nested_model = resolve_model_from_node(datatype)
|
|
50
|
+
Solis::Query::ResultTransformer.new(nested_model).transform(value) if nested_model
|
|
51
|
+
elsif value.is_a?(Array) && value.all?(Hash)
|
|
52
|
+
# Transform array of nested entities
|
|
53
|
+
value.map { |v| transform_nested_entity(property_key, v) }
|
|
54
|
+
else
|
|
55
|
+
value
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def resolve_model_from_node(datatype_node)
|
|
60
|
+
@model.graph.shape_as_model(datatype_node.to_s)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def remove_json_ld_metadata(item)
|
|
65
|
+
item.reject { |key| key.start_with?('@') }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def cast_value(value)
|
|
69
|
+
case value
|
|
70
|
+
when Hash
|
|
71
|
+
handle_typed_value(value)
|
|
72
|
+
when Array
|
|
73
|
+
value.map { |v| cast_value(v) }
|
|
74
|
+
else
|
|
75
|
+
value
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def handle_typed_value(value)
|
|
80
|
+
return value unless value.key?('@type') && value.key?('@value')
|
|
81
|
+
|
|
82
|
+
type = value['@type']
|
|
83
|
+
raw_value = value['@value']
|
|
84
|
+
|
|
85
|
+
cast_by_type(type, raw_value)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def cast_by_type(type, value)
|
|
89
|
+
caster = @type_mappings[type]
|
|
90
|
+
caster ? caster.call(value) : value
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def enforce_cardinality(property_key, value)
|
|
94
|
+
return value if @model.nil?
|
|
95
|
+
|
|
96
|
+
metadata = @cardinality_map[property_key]
|
|
97
|
+
return value if metadata.nil?
|
|
98
|
+
|
|
99
|
+
maxcount = metadata[:maxcount]
|
|
100
|
+
|
|
101
|
+
# If maxcount is nil or > 1, ensure it's an array
|
|
102
|
+
if maxcount.nil? || maxcount > 1
|
|
103
|
+
value.is_a?(Array) ? value : [value]
|
|
104
|
+
# If maxcount is 0 or 1, ensure it's a single value
|
|
105
|
+
else
|
|
106
|
+
value.is_a?(Array) ? value.first : value
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def build_cardinality_map
|
|
111
|
+
return {} if @model.nil? || @model.metadata.nil?
|
|
112
|
+
|
|
113
|
+
attributes = @model.metadata[:attributes] || {}
|
|
114
|
+
|
|
115
|
+
attributes.each_with_object({}) do |(property_name, property_metadata), map|
|
|
116
|
+
map[property_name.to_s] = property_metadata
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def load_type_mappings
|
|
121
|
+
{
|
|
122
|
+
"http://www.w3.org/2001/XMLSchema#dateTime" => ->(v) { DateTime.parse(v) },
|
|
123
|
+
"http://www.w3.org/2001/XMLSchema#date" => ->(v) { Date.parse(v) },
|
|
124
|
+
"http://www.w3.org/2006/time#DateTimeInterval" => ->(v) { ISO8601::TimeInterval.parse(v) },
|
|
125
|
+
"http://www.w3.org/2001/XMLSchema#boolean" => ->(v) { v == "true" },
|
|
126
|
+
"http://www.w3.org/2001/XMLSchema#integer" => ->(v) { v.to_i },
|
|
127
|
+
"http://www.w3.org/2001/XMLSchema#decimal" => ->(v) { BigDecimal(v) }
|
|
128
|
+
}
|
|
129
|
+
end
|
|
130
|
+
end
|
data/lib/solis/query/run.rb
CHANGED
|
@@ -1,115 +1,75 @@
|
|
|
1
1
|
require 'solis/store/sparql/client'
|
|
2
|
+
require 'solis/query/result_transformer'
|
|
2
3
|
require 'solis/config_file'
|
|
3
4
|
|
|
4
5
|
class Solis::Query::Runner
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"@vocab": "#{graph_name}",
|
|
11
|
-
"id": "@id"
|
|
12
|
-
},
|
|
13
|
-
"@type": "#{entity}",
|
|
14
|
-
"@embed": "@always"
|
|
15
|
-
}
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
c = Solis::Store::Sparql::Client.new(Solis::Options.instance.get[:sparql_endpoint], graph_name: graph_name)
|
|
19
|
-
r = c.query(query, options)
|
|
20
|
-
if r.is_a?(SPARQL::Client)
|
|
21
|
-
g = RDF::Graph.new
|
|
22
|
-
t = r.query('select * where{?s ?p ?o}')
|
|
23
|
-
t.each do |s|
|
|
24
|
-
g << [s.s, s.p, s.o]
|
|
25
|
-
end
|
|
6
|
+
def self.run(entity, query, options = {})
|
|
7
|
+
sparql_client = Solis::Store::Sparql::Client.new(
|
|
8
|
+
Solis::Options.instance.get[:sparql_endpoint],
|
|
9
|
+
graph_name: graph_name
|
|
10
|
+
)
|
|
26
11
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
framed = JSON::LD::API.frame(e, context)
|
|
30
|
-
end
|
|
31
|
-
result = sanitize_result(framed)
|
|
32
|
-
else
|
|
33
|
-
t = []
|
|
34
|
-
r.each do |s|
|
|
35
|
-
t << s.to_h
|
|
36
|
-
end
|
|
37
|
-
result = sanitize_result({'@graph' => t})
|
|
38
|
-
end
|
|
39
|
-
result
|
|
40
|
-
rescue StandardError => e
|
|
41
|
-
puts e.message
|
|
42
|
-
raise e
|
|
43
|
-
end
|
|
12
|
+
raw_result = sparql_client.query(query, options)
|
|
13
|
+
model = options[:model] || nil
|
|
44
14
|
|
|
45
|
-
|
|
15
|
+
transform_result(raw_result, entity, model)
|
|
16
|
+
rescue StandardError => e
|
|
17
|
+
puts e.message
|
|
18
|
+
raise e
|
|
19
|
+
end
|
|
46
20
|
|
|
47
|
-
|
|
48
|
-
data = framed&.key?('@graph') ? framed['@graph'] : [framed]
|
|
21
|
+
private
|
|
49
22
|
|
|
50
|
-
|
|
23
|
+
def self.transform_result(raw_result, entity, model)
|
|
24
|
+
if raw_result.is_a?(SPARQL::Client)
|
|
25
|
+
frame_and_transform(raw_result, entity, model)
|
|
26
|
+
else
|
|
27
|
+
transform_select_results(raw_result, model)
|
|
51
28
|
end
|
|
29
|
+
end
|
|
52
30
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if d.is_a?(Hash)
|
|
57
|
-
new_d = {}
|
|
58
|
-
d.each do |k, v|
|
|
59
|
-
if v.is_a?(Hash)
|
|
60
|
-
if v.key?('@type')
|
|
61
|
-
type = v['@type']
|
|
62
|
-
if v.key?('@value')
|
|
63
|
-
value = v['@value']
|
|
64
|
-
case type
|
|
65
|
-
when "http://www.w3.org/2001/XMLSchema#dateTime"
|
|
66
|
-
value = DateTime.parse(value)
|
|
67
|
-
when "http://www.w3.org/2001/XMLSchema#date"
|
|
68
|
-
value = Date.parse(value)
|
|
69
|
-
when "http://www.w3.org/2006/time#DateTimeInterval"
|
|
70
|
-
value = ISO8601::TimeInterval.parse(value)
|
|
71
|
-
when "http://www.w3.org/2001/XMLSchema#boolean"
|
|
72
|
-
value = value == "true"
|
|
73
|
-
end
|
|
74
|
-
v = value
|
|
75
|
-
end
|
|
76
|
-
v = sanitize_result(v) if v.is_a?(Hash)
|
|
77
|
-
end
|
|
78
|
-
if v.is_a?(Hash)
|
|
79
|
-
new_d[k] = v.class.method_defined?(:value) ? v.value : sanitize_result(v)
|
|
80
|
-
else
|
|
81
|
-
new_d[k] = v.class.method_defined?(:value) ? v.value : v
|
|
82
|
-
end
|
|
83
|
-
elsif v.is_a?(Array) #todo: make recursive
|
|
84
|
-
new_d[k] = []
|
|
85
|
-
v.each do |vt|
|
|
86
|
-
if vt.is_a?(Hash)
|
|
87
|
-
if vt.key?('@value')
|
|
88
|
-
new_d[k] << vt['@value']
|
|
89
|
-
else
|
|
90
|
-
new_d[k] << (vt.is_a?(String) ? vt : sanitize_result(vt))
|
|
91
|
-
end
|
|
92
|
-
else
|
|
93
|
-
new_d[k] << (vt.is_a?(String) ? vt : sanitize_result(vt))
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
new_d[k].flatten!
|
|
97
|
-
else
|
|
98
|
-
new_d[k] = v.class.method_defined?(:value) ? v.value : v
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
d = new_d
|
|
102
|
-
end
|
|
31
|
+
def self.frame_and_transform(sparql_result, entity, model)
|
|
32
|
+
graph = build_graph_from_result(sparql_result)
|
|
33
|
+
context = build_context(entity)
|
|
103
34
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
Solis::LOGGER.error(e.message)
|
|
108
|
-
data
|
|
35
|
+
framed = nil
|
|
36
|
+
JSON::LD::API.fromRDF(graph) do |expanded|
|
|
37
|
+
framed = JSON::LD::API.frame(expanded, context)
|
|
109
38
|
end
|
|
110
39
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
40
|
+
Solis::Query::ResultTransformer.new(model).transform(framed)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.transform_select_results(raw_result, model)
|
|
44
|
+
results = raw_result.map(&:to_h)
|
|
45
|
+
Solis::Query::ResultTransformer.new(model).transform({'@graph' => results})
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.build_graph_from_result(sparql_result)
|
|
49
|
+
graph = RDF::Graph.new
|
|
50
|
+
sparql_result.query('select * where{?s ?p ?o}').each do |statement|
|
|
51
|
+
graph << [statement.s, statement.p, statement.o]
|
|
114
52
|
end
|
|
53
|
+
graph
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.build_context(entity)
|
|
57
|
+
JSON.parse(%(
|
|
58
|
+
{
|
|
59
|
+
"@context": {
|
|
60
|
+
"@vocab": "#{graph_name}",
|
|
61
|
+
"id": "@id"
|
|
62
|
+
},
|
|
63
|
+
"@type": "#{entity}",
|
|
64
|
+
"@embed": "@always"
|
|
65
|
+
}
|
|
66
|
+
))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def self.graph_name
|
|
70
|
+
graphs = Solis::Options.instance.get[:graphs]
|
|
71
|
+
raise Solis::Error::NotFoundError, 'No graph name found' if graphs.nil?
|
|
72
|
+
|
|
73
|
+
graphs.find { |g| g['type'].eql?(:main) }&.fetch('name') || ''
|
|
74
|
+
end
|
|
115
75
|
end
|
data/lib/solis/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: solis
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.98.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mehmet Celik
|
|
@@ -299,6 +299,7 @@ files:
|
|
|
299
299
|
- lib/solis/query.rb
|
|
300
300
|
- lib/solis/query/construct.rb
|
|
301
301
|
- lib/solis/query/filter.rb
|
|
302
|
+
- lib/solis/query/result_transformer.rb
|
|
302
303
|
- lib/solis/query/run.rb
|
|
303
304
|
- lib/solis/resource.rb
|
|
304
305
|
- lib/solis/shape.rb
|