rdf 3.0.9 → 3.0.10

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 42d123aacfde151f76648a3593e62fb0821441c67cf8668983becd2b1800ef41
4
- data.tar.gz: 42ecb0370f71b7487f0255af21a8959365138da7c5791c68a3e7aaa2f7a63a2e
3
+ metadata.gz: 3d464dd558551830f9c3948ffc879108e5619ef12384a0df1b0d25c0859a95ab
4
+ data.tar.gz: a3df4023d8417ffa7f94a95b9c2c2f5cb87eecbdcfb8ab89ae8c9b49d63f3060
5
5
  SHA512:
6
- metadata.gz: 22b1e9556b6327e9e8aaaa0f244ffc04565d9c9ef7d075d14f5636672596c40fbb544d49c1bafcc11f2bf90799f85c2ae2fbfb8afbcc6148e5ee357e2d5cde22
7
- data.tar.gz: d86ba20c17b43f887fc5609897cc24814ccf75c2bb4f0d0a8dd5a164e531e55442960e9733792aad628075b811b31f066e18182b8864bdfa0287317280417630
6
+ metadata.gz: 76afd5faf910dcc364f6367590654d69eabbcc409ee6e3b9892b1871c4c760e70f31aea6958394b80b16b04117868acd0adecf602e64b3030a4bf5fa81eb229f
7
+ data.tar.gz: 277b395451e0109f442a86ae41da2cebac078d41799beec31194d38e0cea4163092bb756ef8f435ead80d6078c48c27ec2a00b9c569be4044e36e9cd3e4b870b
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.0.9
1
+ 3.0.10
@@ -7,7 +7,7 @@ module RDF; class Literal
7
7
  class DateTime < Literal
8
8
  DATATYPE = RDF::URI("http://www.w3.org/2001/XMLSchema#dateTime")
9
9
  GRAMMAR = %r(\A(-?(?:\d{4}|[1-9]\d{4,})-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?)((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z).freeze
10
- FORMAT = '%Y-%m-%dT%H:%M:%S%:z'.freeze
10
+ FORMAT = '%Y-%m-%dT%H:%M:%S.%L%:z'.freeze
11
11
 
12
12
  ##
13
13
  # @param [DateTime] value
@@ -31,9 +31,9 @@ module RDF; class Literal
31
31
  def canonicalize!
32
32
  if self.valid?
33
33
  @string = if has_timezone?
34
- @object.new_offset.new_offset.strftime(FORMAT[0..-4] + 'Z')
34
+ @object.new_offset.new_offset.strftime(FORMAT[0..-4] + 'Z').sub('.000', '')
35
35
  else
36
- @object.strftime(FORMAT[0..-4])
36
+ @object.strftime(FORMAT[0..-4]).sub('.000', '')
37
37
  end
38
38
  end
39
39
  self
@@ -80,6 +80,16 @@ module RDF; class Literal
80
80
  super && object && value !~ %r(\A0000)
81
81
  end
82
82
 
83
+ ##
84
+ # Does the literal representation include millisectonds?
85
+ #
86
+ # @return [Boolean]
87
+ # @since 1.1.6
88
+ def has_milliseconds?
89
+ self.format("%L").to_i > 0
90
+ end
91
+ alias_method :has_ms?, :has_milliseconds?
92
+
83
93
  ##
84
94
  # Does the literal representation include a timezone? Note that this is only possible if initialized using a string, or `:lexical` option.
85
95
  #
@@ -98,7 +108,7 @@ module RDF; class Literal
98
108
  #
99
109
  # @return [String]
100
110
  def to_s
101
- @string || @object.strftime(FORMAT).sub("+00:00", 'Z')
111
+ @string || @object.strftime(FORMAT).sub("+00:00", 'Z').sub('.000', '')
102
112
  end
103
113
 
104
114
  ##
@@ -12,7 +12,7 @@ module RDF; class Literal
12
12
  class Time < Literal
13
13
  DATATYPE = RDF::URI("http://www.w3.org/2001/XMLSchema#time")
14
14
  GRAMMAR = %r(\A(\d{2}:\d{2}:\d{2}(?:\.\d+)?)((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z).freeze
15
- FORMAT = '%H:%M:%S%:z'.freeze
15
+ FORMAT = '%H:%M:%S.%L%:z'.freeze
16
16
 
17
17
  ##
18
18
  # @param [String, DateTime, #to_datetime] value
@@ -43,9 +43,9 @@ module RDF; class Literal
43
43
  def canonicalize!
44
44
  if self.valid?
45
45
  @string = if has_timezone?
46
- @object.new_offset.new_offset.strftime(FORMAT[0..-4] + 'Z')
46
+ @object.new_offset.new_offset.strftime(FORMAT[0..-4] + 'Z').sub('.000', '')
47
47
  else
48
- @object.strftime(FORMAT[0..-4])
48
+ @object.strftime(FORMAT[0..-4]).sub('.000', '')
49
49
  end
50
50
  end
51
51
  self
@@ -91,7 +91,7 @@ module RDF; class Literal
91
91
  #
92
92
  # @return [String]
93
93
  def to_s
94
- @string || @object.strftime(FORMAT).sub("+00:00", 'Z')
94
+ @string || @object.strftime(FORMAT).sub("+00:00", 'Z').sub('.000', '')
95
95
  end
96
96
 
97
97
  ##
@@ -122,7 +122,7 @@ module RDF; class Literal
122
122
  return super unless other.valid?
123
123
  # Compare as strings, as time includes a date portion, and adjusting for UTC
124
124
  # can create a mismatch in the date portion.
125
- self.object.new_offset.strftime('%H%M%S') == other.object.new_offset.strftime('%H%M%S')
125
+ self.object.new_offset.strftime('%H%M%S.%L') == other.object.new_offset.strftime('%H%M%S.%L')
126
126
  when Literal::DateTime, Literal::Date
127
127
  false
128
128
  else
@@ -57,6 +57,9 @@ module RDF
57
57
  # @return [RDF::Term]
58
58
  attr_accessor :object
59
59
 
60
+ # @return [Hash{Symbol => Object}]
61
+ attr_accessor :options
62
+
60
63
  ##
61
64
  # @overload initialize(**options)
62
65
  # @param [Hash{Symbol => Object}] options
@@ -169,8 +169,7 @@ module RDF
169
169
  def self.normalize_path(path)
170
170
  output, input = "", path.to_s
171
171
  if input.encoding != Encoding::ASCII_8BIT
172
- input = input.dup if input.frozen?
173
- input = input.force_encoding(Encoding::ASCII_8BIT)
172
+ input = input.dup.force_encoding(Encoding::ASCII_8BIT)
174
173
  end
175
174
  until input.empty?
176
175
  if input.match(RDS_2A)
@@ -228,8 +227,7 @@ module RDF
228
227
  if uri
229
228
  @value = uri.to_s
230
229
  if @value.encoding != Encoding::UTF_8
231
- @value = @value.dup if @value.frozen?
232
- @value.force_encoding(Encoding::UTF_8)
230
+ @value.dup.force_encoding(Encoding::UTF_8)
233
231
  @value.freeze
234
232
  end
235
233
  else
@@ -842,16 +840,16 @@ module RDF
842
840
  user, password = userinfo.to_s.split(':', 2)
843
841
  host, port = hostport.to_s.split(':', 2)
844
842
 
845
- parts[:scheme] = (scheme.force_encoding(Encoding::UTF_8) if scheme)
846
- parts[:authority] = (authority.force_encoding(Encoding::UTF_8) if authority)
847
- parts[:userinfo] = (userinfo.force_encoding(Encoding::UTF_8) if userinfo)
848
- parts[:user] = (user.force_encoding(Encoding::UTF_8) if user)
849
- parts[:password] = (password.force_encoding(Encoding::UTF_8) if password)
850
- parts[:host] = (host.force_encoding(Encoding::UTF_8) if host)
843
+ parts[:scheme] = (scheme.dup.force_encoding(Encoding::UTF_8) if scheme)
844
+ parts[:authority] = (authority.dup.force_encoding(Encoding::UTF_8) if authority)
845
+ parts[:userinfo] = (userinfo.dup.force_encoding(Encoding::UTF_8) if userinfo)
846
+ parts[:user] = (user.dup.force_encoding(Encoding::UTF_8) if user)
847
+ parts[:password] = (password.dup.force_encoding(Encoding::UTF_8) if password)
848
+ parts[:host] = (host.dup.force_encoding(Encoding::UTF_8) if host)
851
849
  parts[:port] = (::URI.decode(port).to_i if port)
852
- parts[:path] = (path.to_s.force_encoding(Encoding::UTF_8) unless path.empty?)
853
- parts[:query] = (query[1..-1].force_encoding(Encoding::UTF_8) if query)
854
- parts[:fragment] = (fragment[1..-1].force_encoding(Encoding::UTF_8) if fragment)
850
+ parts[:path] = (path.to_s.dup.force_encoding(Encoding::UTF_8) unless path.empty?)
851
+ parts[:query] = (query[1..-1].dup.force_encoding(Encoding::UTF_8) if query)
852
+ parts[:fragment] = (fragment[1..-1].dup.force_encoding(Encoding::UTF_8) if fragment)
855
853
  end
856
854
 
857
855
  parts
@@ -869,7 +867,7 @@ module RDF
869
867
  # @param [String, #to_s] value
870
868
  # @return [RDF::URI] self
871
869
  def scheme=(value)
872
- object[:scheme] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
870
+ object[:scheme] = (value.to_s.dup.force_encoding(Encoding::UTF_8) if value)
873
871
  @value = nil
874
872
  self
875
873
  end
@@ -893,7 +891,7 @@ module RDF
893
891
  # @param [String, #to_s] value
894
892
  # @return [RDF::URI] self
895
893
  def user=(value)
896
- object[:user] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
894
+ object[:user] = (value.to_s.dup.force_encoding(Encoding::UTF_8) if value)
897
895
  @object[:userinfo] = format_userinfo("")
898
896
  @object[:authority] = format_authority
899
897
  @value = nil
@@ -919,7 +917,7 @@ module RDF
919
917
  # @param [String, #to_s] value
920
918
  # @return [RDF::URI] self
921
919
  def password=(value)
922
- object[:password] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
920
+ object[:password] = (value.to_s.dup.force_encoding(Encoding::UTF_8) if value)
923
921
  @object[:userinfo] = format_userinfo("")
924
922
  @object[:authority] = format_authority
925
923
  @value = nil
@@ -947,7 +945,7 @@ module RDF
947
945
  # @param [String, #to_s] value
948
946
  # @return [RDF::URI] self
949
947
  def host=(value)
950
- object[:host] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
948
+ object[:host] = (value.to_s.dup.force_encoding(Encoding::UTF_8) if value)
951
949
  @object[:authority] = format_authority
952
950
  @value = nil
953
951
  self
@@ -1010,7 +1008,7 @@ module RDF
1010
1008
  if value
1011
1009
  # Always lead with a slash
1012
1010
  value = "/#{value}" if host && value.to_s.match?(/^[^\/]/)
1013
- object[:path] = value.to_s.force_encoding(Encoding::UTF_8)
1011
+ object[:path] = value.to_s.dup.force_encoding(Encoding::UTF_8)
1014
1012
  else
1015
1013
  object[:path] = nil
1016
1014
  end
@@ -1069,7 +1067,7 @@ module RDF
1069
1067
  # @param [String, #to_s] value
1070
1068
  # @return [RDF::URI] self
1071
1069
  def query=(value)
1072
- object[:query] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
1070
+ object[:query] = (value.to_s.dup.force_encoding(Encoding::UTF_8) if value)
1073
1071
  @value = nil
1074
1072
  self
1075
1073
  end
@@ -1093,7 +1091,7 @@ module RDF
1093
1091
  # @param [String, #to_s] value
1094
1092
  # @return [RDF::URI] self
1095
1093
  def fragment=(value)
1096
- object[:fragment] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
1094
+ object[:fragment] = (value.to_s.dup.force_encoding(Encoding::UTF_8) if value)
1097
1095
  @value = nil
1098
1096
  self
1099
1097
  end
@@ -1118,7 +1116,7 @@ module RDF
1118
1116
  # @return [RDF::URI] self
1119
1117
  def authority=(value)
1120
1118
  object.delete_if {|k, v| [:user, :password, :host, :port, :userinfo].include?(k)}
1121
- object[:authority] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
1119
+ object[:authority] = (value.to_s.dup.force_encoding(Encoding::UTF_8) if value)
1122
1120
  user; password; userinfo; host; port
1123
1121
  @value = nil
1124
1122
  self
@@ -1148,7 +1146,7 @@ module RDF
1148
1146
  # @return [RDF::URI] self
1149
1147
  def userinfo=(value)
1150
1148
  object.delete_if {|k, v| [:user, :password, :authority].include?(k)}
1151
- object[:userinfo] = (value.to_s.force_encoding(Encoding::UTF_8) if value)
1149
+ object[:userinfo] = (value.to_s.dup.force_encoding(Encoding::UTF_8) if value)
1152
1150
  user; password; authority
1153
1151
  @value = nil
1154
1152
  self
@@ -1263,6 +1261,26 @@ module RDF
1263
1261
  return res
1264
1262
  end
1265
1263
 
1264
+ ##
1265
+ # Dump of data needed to reconsitute this object using Marshal.load
1266
+ # This override is needed to avoid serializing @mutex.
1267
+ #
1268
+ # @param [Integer] level The maximum depth of objects to dump.
1269
+ # @return [String] The dump of data needed to reconsitute this object.
1270
+ def _dump(level)
1271
+ value
1272
+ end
1273
+
1274
+ ##
1275
+ # Load dumped data to reconsitute marshaled object
1276
+ # This override is needed to avoid serializing @mutex.
1277
+ #
1278
+ # @param [String] data The dump of data needed to reconsitute this object.
1279
+ # @return [RDF::URI] The reconsituted object.
1280
+ def self._load(data)
1281
+ new(data)
1282
+ end
1283
+
1266
1284
  private
1267
1285
 
1268
1286
  ##
@@ -1274,8 +1292,7 @@ module RDF
1274
1292
  # @return [String]
1275
1293
  def normalize_segment(value, expr, downcase = false)
1276
1294
  if value
1277
- value = value.dup if value.frozen?
1278
- value = value.force_encoding(Encoding::UTF_8)
1295
+ value = value.dup.force_encoding(Encoding::UTF_8)
1279
1296
  decoded = ::URI.decode(value)
1280
1297
  decoded.downcase! if downcase
1281
1298
  ::URI.encode(decoded, /[^(?:#{expr})]/)
@@ -32,7 +32,7 @@ module RDF::NTriples
32
32
  format RDF::NTriples::Format
33
33
 
34
34
  # @see http://www.w3.org/TR/rdf-testcases/#ntrip_strings
35
- ESCAPE_CHARS = ["\b", "\f", "\t", "\n", "\r", "\"", "\\"].freeze
35
+ ESCAPE_CHARS = ["\b", "\f", "\t", "\n", "\r", "\"", "'", "\\"].freeze
36
36
  UCHAR4 = /\\u([0-9A-Fa-f]{4,4})/.freeze
37
37
  UCHAR8 = /\\U([0-9A-Fa-f]{8,8})/.freeze
38
38
  UCHAR = Regexp.union(UCHAR4, UCHAR8).freeze
@@ -60,7 +60,7 @@ module RDF::NTriples
60
60
  # 166s
61
61
  PN_CHARS = /-|[0-9]|#{PN_CHARS_U}|#{U_CHARS2}/.freeze
62
62
  # 159s
63
- ECHAR = /\\[tbnrf\\"]/.freeze
63
+ ECHAR = /\\[tbnrf"'\\]/.freeze
64
64
  # 18
65
65
  IRIREF = /<((?:#{IRI_RANGE}|#{UCHAR})*)>/.freeze
66
66
  # 141s
@@ -135,7 +135,6 @@ module RDF::NTriples
135
135
  # @return [RDF::URI]
136
136
  def self.parse_uri(input, intern: false, **options)
137
137
  if input =~ URIREF
138
- uri_str = unescape($1)
139
138
  RDF::URI.send(intern ? :intern : :new, unescape($1))
140
139
  end
141
140
  end
@@ -155,9 +154,16 @@ module RDF::NTriples
155
154
  end
156
155
 
157
156
  # cache constants to optimize escaping the escape chars in self.unescape
158
- ESCAPE_CHARS_ESCAPED = ESCAPE_CHARS.each_with_object({}) do |escape, memo|
159
- memo[escape.inspect[1...-1]] = escape
160
- end.freeze
157
+ ESCAPE_CHARS_ESCAPED = {
158
+ "\\b" => "\b",
159
+ "\\f" => "\f",
160
+ "\\t" => "\t",
161
+ "\\n" => "\n",
162
+ "\\r" => "\r",
163
+ "\\\"" => "\"",
164
+ "\\'" => "'",
165
+ "\\\\" => "\\"
166
+ } .freeze
161
167
  ESCAPE_CHARS_ESCAPED_REGEXP = Regexp.union(
162
168
  ESCAPE_CHARS_ESCAPED.keys
163
169
  ).freeze
@@ -171,26 +177,23 @@ module RDF::NTriples
171
177
  def self.unescape(string)
172
178
  # Note: avoiding copying the input string when no escaping is needed
173
179
  # greatly reduces the number of allocations and the processing time.
174
- unless string.encoding == Encoding::UTF_8
175
- string = string.dup.force_encoding(Encoding::UTF_8)
176
- end
177
-
178
- has_escape_chars = ESCAPE_CHARS_ESCAPED_REGEXP.match?(string)
179
- has_uchar = UCHAR.match?(string)
180
-
181
- string = string.dup if has_escape_chars || has_uchar
180
+ string = string.dup.force_encoding(Encoding::UTF_8) unless string.encoding == Encoding::UTF_8
181
+ scanner = StringScanner.new(string)
182
182
 
183
- # Decode \t|\n|\r|\"|\\ character escapes using Regexp:
184
- string.gsub!(ESCAPE_CHARS_ESCAPED_REGEXP) do
185
- ESCAPE_CHARS_ESCAPED.fetch($~[0])
186
- end if has_escape_chars
183
+ buffer = ""
187
184
 
188
- # Decode \uXXXX and \UXXXXXXXX code points:
189
- string.gsub!(UCHAR) do
190
- [($1 || $2).hex].pack('U*')
191
- end if has_uchar
185
+ while !scanner.eos?
186
+ buffer << if scanner.scan(ESCAPE_CHARS_ESCAPED_REGEXP)
187
+ ESCAPE_CHARS_ESCAPED[scanner.matched]
188
+ elsif scanner.scan(UCHAR)
189
+ scanner.matched.sub(UCHAR) {[($1 || $2).hex].pack('U*')}
190
+ else
191
+ # Scan one character
192
+ scanner.getch
193
+ end
194
+ end
192
195
 
193
- string
196
+ buffer
194
197
  end
195
198
 
196
199
  ##
@@ -250,7 +253,7 @@ module RDF::NTriples
250
253
  uri.canonicalize! if canonicalize?
251
254
  uri
252
255
  end
253
- rescue ArgumentError => e
256
+ rescue ArgumentError
254
257
  log_error("Invalid URI (found: \"<#{uri_str}>\")", lineno: lineno, token: "<#{uri_str}>", exception: RDF::ReaderError)
255
258
  end
256
259
 
@@ -258,7 +261,7 @@ module RDF::NTriples
258
261
  # @return [RDF::Node]
259
262
  # @see http://www.w3.org/TR/rdf-testcases/#ntrip_grammar (nodeID)
260
263
  def read_node
261
- if node_id = match(NODEID)
264
+ if node_id = match(NODEID)
262
265
  @nodes ||= {}
263
266
  @nodes[node_id] ||= RDF::Node.new(node_id)
264
267
  end
@@ -42,7 +42,7 @@ module RDF::NTriples
42
42
  format RDF::NTriples::Format
43
43
 
44
44
  # @see http://www.w3.org/TR/rdf-testcases/#ntrip_strings
45
- ESCAPE_PLAIN = /\A[\x20-\x21\x23-#{Regexp.escape '['}#{Regexp.escape ']'}-\x7E]*\z/m.freeze
45
+ ESCAPE_PLAIN = /\A[\x20-\x21\x23-\x26\x28#{Regexp.escape '['}#{Regexp.escape ']'}-\x7E]*\z/m.freeze
46
46
  ESCAPE_PLAIN_U = /\A(?:#{Reader::IRI_RANGE}|#{Reader::UCHAR})*\z/.freeze
47
47
 
48
48
  ##
@@ -116,6 +116,8 @@ module RDF::NTriples
116
116
  # sequences, otherwise, assume the test-cases escape sequences. Otherwise,
117
117
  # the N-Triples recommendation includes `\b` and `\f` escape sequences.
118
118
  #
119
+ # Within STRING_LITERAL_QUOTE, only the characters `U+0022`, `U+005C`, `U+000A`, `U+000D` are encoded using `ECHAR`. `ECHAR` must not be used for characters that are allowed directly in STRING_LITERAL_QUOTE.
120
+ #
119
121
  # @param [Integer, #ord] u
120
122
  # @return [String]
121
123
  # @raise [ArgumentError] if `u` is not a valid Unicode codepoint
@@ -124,11 +126,7 @@ module RDF::NTriples
124
126
  def self.escape_ascii(u, encoding)
125
127
  case (u = u.ord)
126
128
  when (0x00..0x07) then escape_utf16(u)
127
- when (0x08) then (encoding && encoding == Encoding::ASCII ? escape_utf16(u) : "\\b")
128
- when (0x09) then "\\t"
129
129
  when (0x0A) then "\\n"
130
- when (0x0B) then escape_utf16(u)
131
- when (0x0C) then (encoding && encoding == Encoding::ASCII ? escape_utf16(u) : "\\f")
132
130
  when (0x0D) then "\\r"
133
131
  when (0x0E..0x1F) then escape_utf16(u)
134
132
  when (0x22) then "\\\""
@@ -264,7 +262,7 @@ module RDF::NTriples
264
262
  string.each_char do |u|
265
263
  buffer << case u.ord
266
264
  when (0x00..0x20) then self.class.escape_utf16(u)
267
- when 0x22, 0x3c, 0x3e, 0x5c, 0x5e, 0x60, 0x7b, 0x7c, 0x7d # <>"{}|`\
265
+ when 0x22, 0x3c, 0x3e, 0x5c, 0x5e, 0x60, 0x7b, 0x7c, 0x7d # "<>\^`{|}
268
266
  self.class.escape_utf16(u)
269
267
  else u
270
268
  end
@@ -278,7 +276,7 @@ module RDF::NTriples
278
276
  string.each_byte do |u|
279
277
  buffer << case u
280
278
  when (0x00..0x20) then self.class.escape_utf16(u)
281
- when 0x22, 0x3c, 0x3e, 0x5c, 0x5e, 0x60, 0x7b, 0x7c, 0x7d # <>"{}|`\
279
+ when 0x22, 0x3c, 0x3e, 0x5c, 0x5e, 0x60, 0x7b, 0x7c, 0x7d # "<>\^`{|}
282
280
  self.class.escape_utf16(u)
283
281
  when (0x80..0xFFFF) then self.class.escape_utf16(u)
284
282
  when (0x10000..0x10FFFF) then self.class.escape_utf32(u)
@@ -171,10 +171,12 @@ module RDF
171
171
  # Alias for `:graph_name`.
172
172
  # @param [Hash{Symbol => Object}] options
173
173
  # any additional keyword options
174
+ # @option options [Boolean] validate (false)
175
+ # validate patterns
174
176
  # @yield [query]
175
177
  # @yieldparam [RDF::Query] query
176
178
  # @yieldreturn [void] ignored
177
- def initialize(*patterns, solutions: nil, graph_name: nil, name: nil, **options, &block)
179
+ def initialize(*patterns, solutions: nil, graph_name: nil, name: nil, validate: false, **options, &block)
178
180
  @options = options.dup
179
181
  @solutions = Query::Solutions(solutions)
180
182
  graph_name = name if graph_name.nil?
@@ -195,6 +197,8 @@ module RDF
195
197
  else instance_eval(&block)
196
198
  end
197
199
  end
200
+
201
+ validate! if validate
198
202
  end
199
203
 
200
204
  ##
@@ -291,7 +295,6 @@ module RDF
291
295
  # @see http://www.holygoat.co.uk/blog/entry/2005-10-25-1
292
296
  # @see http://www.w3.org/TR/sparql11-query/#emptyGroupPattern
293
297
  def execute(queryable, solutions: Solution.new, graph_name: nil, name: nil, **options, &block)
294
- validate!
295
298
  options = {bindings: {}}.merge(options)
296
299
 
297
300
  # Use provided solutions to allow for query chaining
@@ -356,8 +359,9 @@ module RDF
356
359
  return @solutions if @solutions.empty?
357
360
 
358
361
  if !pattern.optional?
359
- # We have no solutions for variables we should have solutions for:
360
- need_vars = pattern.variables.keys
362
+ # We have no solutions for variables we should have solutions for
363
+ # (excludes non-distinguished variables):
364
+ need_vars = pattern.variables.select {|k,v| v.distinguished?}.keys
361
365
  @solutions.each do |solution|
362
366
  break if need_vars.empty?
363
367
  need_vars -= solution.bindings.keys
@@ -516,12 +520,12 @@ module RDF
516
520
  end
517
521
 
518
522
  ##
519
- # Determine if the URI is a valid according to RFC3987
523
+ # Determine if the query containts valid patterns
520
524
  #
521
525
  # @return [Boolean] `true` or `false`
522
526
  # @since 0.3.9
523
527
  def valid?
524
- !!validate!
528
+ !!validate! rescue raise false
525
529
  rescue
526
530
  false
527
531
  end
@@ -47,6 +47,13 @@ module RDF; class Query
47
47
  @subject = Variable.new(@subject) if @subject.is_a?(Symbol)
48
48
  @predicate = Variable.new(@predicate) if @predicate.is_a?(Symbol)
49
49
  @object = Variable.new(@object) if @object.is_a?(Symbol)
50
+
51
+ # Estmate cost positionally, with variables being least expensive as objects, then predicates, then subjects, then graph_names.
52
+ # XXX does not consider bound variables, which would need to be dynamically calculated.
53
+ @cost = (@object.nil? || @object.is_a?(Variable) ? 1 : 0) +
54
+ (@predicate.nil? || @predicate.is_a?(Variable) ? 2 : 0) +
55
+ (@subject.nil? || @subject.is_a?(Variable) ? 4 : 0) +
56
+ (@graph_name.is_a?(Variable) ? 8 : 0)
50
57
  super
51
58
  end
52
59
 
@@ -22,6 +22,7 @@ class RDF::Query
22
22
  #
23
23
  class Solution
24
24
  # Undefine all superfluous instance methods:
25
+ alias_method :__send, :send
25
26
  undef_method(*instance_methods.
26
27
  map(&:to_s).
27
28
  select {|m| m.match?(/^\w+$/)}.
@@ -57,10 +58,20 @@ class RDF::Query
57
58
  # @yieldparam [RDF::Term] value
58
59
  # @return [Enumerator]
59
60
  def each_binding(&block)
60
- @bindings.each(&block)
61
+ @bindings.each(&block) if block_given?
62
+ enum_binding
61
63
  end
62
64
  alias_method :each, :each_binding
63
65
 
66
+ ##
67
+ # Returns an enumerator for {#each_binding}.
68
+ #
69
+ # @return [Enumerator<RDF::Resource>]
70
+ # @see #each_subject
71
+ def enum_binding
72
+ enum_for(:each_binding)
73
+ end
74
+
64
75
  ##
65
76
  # Enumerates over every variable name in this solution.
66
77
  #
@@ -68,10 +79,20 @@ class RDF::Query
68
79
  # @yieldparam [Symbol] name
69
80
  # @return [Enumerator]
70
81
  def each_name(&block)
71
- @bindings.each_key(&block)
82
+ @bindings.each_key(&block) if block_given?
83
+ enum_name
72
84
  end
73
85
  alias_method :each_key, :each_name
74
86
 
87
+ ##
88
+ # Returns an enumerator for {#each_name}.
89
+ #
90
+ # @return [Enumerator<RDF::Resource>]
91
+ # @see #each_subject
92
+ def enum_name
93
+ enum_for(:each_name)
94
+ end
95
+
75
96
  ##
76
97
  # Enumerates over every variable value in this solution.
77
98
  #
@@ -79,7 +100,17 @@ class RDF::Query
79
100
  # @yieldparam [RDF::Term] value
80
101
  # @return [Enumerator]
81
102
  def each_value(&block)
82
- @bindings.each_value(&block)
103
+ @bindings.each_value(&block) if block_given?
104
+ enum_value
105
+ end
106
+
107
+ ##
108
+ # Returns an enumerator for {#each_value}.
109
+ #
110
+ # @return [Enumerator<RDF::Resource>]
111
+ # @see #each_subject
112
+ def enum_value
113
+ enum_for(:each_value)
83
114
  end
84
115
 
85
116
  ##
@@ -101,9 +132,21 @@ class RDF::Query
101
132
  # @yieldparam [Variable]
102
133
  # @return [Enumerator]
103
134
  def each_variable
104
- @bindings.each do |name, value|
105
- yield Variable.new(name, value)
135
+ if block_given?
136
+ @bindings.each do |name, value|
137
+ yield Variable.new(name, value)
138
+ end
106
139
  end
140
+ enum_variable
141
+ end
142
+
143
+ ##
144
+ # Returns an enumerator for {#each_variable}.
145
+ #
146
+ # @return [Enumerator<RDF::Resource>]
147
+ # @see #each_subject
148
+ def enum_variable
149
+ enum_for(:each_variable)
107
150
  end
108
151
 
109
152
  ##
@@ -282,5 +325,19 @@ class RDF::Query
282
325
  def respond_to_missing?(name, include_private = false)
283
326
  @bindings.has_key?(name.to_sym) || super
284
327
  end
328
+
329
+ ##
330
+ # @private
331
+ # @param [Symbol, #to_sym] method
332
+ # @return [Enumerator]
333
+ # @see Object#enum_for
334
+ def enum_for(method = :each)
335
+ # Ensure that enumerators are, themselves, queryable
336
+ this = self
337
+ Enumerator.new do |yielder|
338
+ this.__send(method) {|*y| yielder << (y.length > 1 ? y : y.first)}
339
+ end
340
+ end
341
+ alias_method :to_enum, :enum_for
285
342
  end # Solution
286
343
  end # RDF::Query
@@ -104,7 +104,29 @@ module RDF; class Query
104
104
  end
105
105
  bindings
106
106
  end
107
-
107
+
108
+ ##
109
+ # Duplicates each solution.
110
+ # @return [RDF::Query::Solutions]
111
+ def dup
112
+ RDF::Query::Solutions.new(self.compact.map(&:dup))
113
+ end
114
+
115
+ ##
116
+ # Merge solutions in `other` into a new solutions instance. Each solution in `other` is merged into those solutions in `self` that are compatible.
117
+ #
118
+ # @param [RDF::Query::Solutions] other
119
+ # @return [RDF::Query::Solutions]
120
+ def merge(other)
121
+ other ||= RDF::Query::Solutions()
122
+ return other if self.empty?
123
+ return self if other.empty?
124
+
125
+ RDF::Query::Solutions(self.map do |s1|
126
+ other.map { |s2| s2.merge(s1) if s2.compatible?(s1) }
127
+ end.flatten.compact)
128
+ end
129
+
108
130
  ##
109
131
  # Filters this solution sequence by the given `criteria`.
110
132
  #
@@ -65,9 +65,25 @@ class RDF::Query
65
65
  # the variable name
66
66
  # @param [RDF::Term] value
67
67
  # an optional variable value
68
- def initialize(name = nil, value = nil)
69
- @name = (name || "g#{__id__.to_i.abs}").to_sym
68
+ # @param [Boolean] distinguished (true) Also interpreted by leading '??' or '$$' in name.
69
+ # @param [Boolean] existential (true) Also interpreted by leading '$' in name
70
+ def initialize(name = nil, value = nil, distinguished: nil, existential: nil)
71
+ name = (name || "g#{__id__.to_i.abs}").to_s
72
+ if name.start_with?('??')
73
+ name, dis, ex = name[2..-1], false, false
74
+ elsif name.start_with?('?')
75
+ name, dis, ex = name[1..-1], true, false
76
+ elsif name.start_with?('$$')
77
+ name, dis, ex = name[2..-1], false, true
78
+ elsif name.start_with?('$')
79
+ name, dis, ex = name[1..-1], true, true
80
+ else
81
+ dis, ex = true, false
82
+ end
83
+ @name = name.to_sym
70
84
  @value = value
85
+ @distinguished = distinguished.nil? ? dis : distinguished
86
+ @existential = existential.nil? ? ex : existential
71
87
  end
72
88
 
73
89
  ##
@@ -109,7 +125,7 @@ class RDF::Query
109
125
  #
110
126
  # @return [Boolean]
111
127
  def distinguished?
112
- @distinguished.nil? || @distinguished
128
+ @distinguished
113
129
  end
114
130
 
115
131
  ##
@@ -121,6 +137,23 @@ class RDF::Query
121
137
  @distinguished = value
122
138
  end
123
139
 
140
+ ##
141
+ # Returns `true` if this variable is existential.
142
+ #
143
+ # @return [Boolean]
144
+ def existential?
145
+ @existential
146
+ end
147
+
148
+ ##
149
+ # Sets if variable is existential or univeresal.
150
+ # By default, variables are universal
151
+ #
152
+ # @return [Boolean]
153
+ def existential=(value)
154
+ @existential = value
155
+ end
156
+
124
157
  ##
125
158
  # Rebinds this variable to the given `value`.
126
159
  #
@@ -208,6 +241,7 @@ class RDF::Query
208
241
  #
209
242
  # Non-distinguished variables are indicated with a double `??`
210
243
  #
244
+ # Existential variables are indicated using a single `$`, or with `$$` if also non-distinguished
211
245
  # @example
212
246
  # v = Variable.new("a")
213
247
  # v.to_s => '?a'
@@ -216,7 +250,7 @@ class RDF::Query
216
250
  #
217
251
  # @return [String]
218
252
  def to_s
219
- prefix = distinguished? ? '?' : "??"
253
+ prefix = distinguished? ? (existential? ? '$' : '?') : (existential? ? '$$' : '??')
220
254
  unbound? ? "#{prefix}#{name}" : "#{prefix}#{name}=#{value}"
221
255
  end
222
256
  end # Variable
@@ -206,8 +206,9 @@ module RDF
206
206
  # @private
207
207
  # @see RDF::Enumerable#project_graph
208
208
  def project_graph(graph_name, &block)
209
- RDF::Graph.new(graph_name: graph_name, data: self).
210
- project_graph(graph_name, &block)
209
+ graph = RDF::Graph.new(graph_name: graph_name, data: self)
210
+ graph.each(&block) if block_given?
211
+ graph
211
212
  end
212
213
 
213
214
  ##
@@ -324,8 +325,8 @@ module RDF
324
325
  @data.each do |g, ss|
325
326
  ss.each do |s, ps|
326
327
  ps.each do |p, os|
327
- os.each do |o|
328
- yield RDF::Statement.new(s, p, o, graph_name: g.equal?(DEFAULT_GRAPH) ? nil : g)
328
+ os.each do |o, object_options|
329
+ yield RDF::Statement.new(s, p, o, object_options.merge(graph_name: g.equal?(DEFAULT_GRAPH) ? nil : g))
329
330
  end
330
331
  end
331
332
  end
@@ -402,9 +403,9 @@ module RDF
402
403
  []
403
404
  end
404
405
  ps.each do |p, os|
405
- os.each do |o|
406
+ os.each do |o, object_options|
406
407
  next unless object.nil? || object.eql?(o)
407
- yield RDF::Statement.new(s, p, o, graph_name: c.equal?(DEFAULT_GRAPH) ? nil : c)
408
+ yield RDF::Statement.new(s, p, o, object_options.merge(graph_name: c.equal?(DEFAULT_GRAPH) ? nil : c))
408
409
  end
409
410
  end
410
411
  end
@@ -461,7 +462,7 @@ module RDF
461
462
  data.has_key?(g) &&
462
463
  data[g].has_key?(s) &&
463
464
  data[g][s].has_key?(p) &&
464
- data[g][s][p].include?(o)
465
+ data[g][s][p].has_key?(o)
465
466
  end
466
467
 
467
468
  ##
@@ -475,9 +476,9 @@ module RDF
475
476
  c ||= DEFAULT_GRAPH
476
477
 
477
478
  return data.put(c) do |subs|
478
- subs = (subs || Hamster::Hash.new).put(s) do |preds|
479
- preds = (preds || Hamster::Hash.new).put(p) do |objs|
480
- (objs || Hamster::Set.new).add(o)
479
+ (subs || Hamster::Hash.new).put(s) do |preds|
480
+ (preds || Hamster::Hash.new).put(p) do |objs|
481
+ (objs || Hamster::Hash.new).put(o, statement.options)
481
482
  end
482
483
  end
483
484
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdf
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.9
4
+ version: 3.0.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arto Bendiken
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2019-01-01 00:00:00.000000000 Z
13
+ date: 2019-02-11 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: link_header
@@ -297,7 +297,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
297
297
  - !ruby/object:Gem::Version
298
298
  version: '0'
299
299
  requirements: []
300
- rubygems_version: 3.0.1
300
+ rubygems_version: 3.0.2
301
301
  signing_key:
302
302
  specification_version: 4
303
303
  summary: A Ruby library for working with Resource Description Framework (RDF) data.