1use Test::More;
2use Test::Exception;
3
4use utf8;
5use strict;
6use warnings;
7no warnings 'redefine';
8
9use DBI;
10use RDF::Trine qw(literal);
11use RDF::Trine::Model;
12use RDF::Trine::Node;
13use RDF::Trine::Pattern;
14use RDF::Trine::Namespace;
15use RDF::Trine::Store::DBI;
16use RDF::Trine::Statement;
17use File::Temp qw(tempfile);
18
19my $rdf		= RDF::Trine::Namespace->new('http://www.w3.org/1999/02/22-rdf-syntax-ns#');
20my $foaf	= RDF::Trine::Namespace->new('http://xmlns.com/foaf/0.1/');
21my $xsd		= RDF::Trine::Namespace->new('http://www.w3.org/2001/XMLSchema#');
22my $kasei	= RDF::Trine::Namespace->new('http://kasei.us/');
23my $b		= RDF::Trine::Node::Blank->new();
24my $p		= RDF::Trine::Node::Resource->new('http://kasei.us/about/foaf.xrdf#greg');
25my $intval	= RDF::Trine::Node::Literal->new('23',undef,$xsd->int);
26my $langval	= RDF::Trine::Node::Literal->new('gwilliams','en');
27my $st0		= RDF::Trine::Statement->new( $p, $rdf->type, $foaf->Person );
28my $st1		= RDF::Trine::Statement->new( $p, $foaf->name, RDF::Trine::Node::Literal->new('Gregory Todd Williams') );
29my $st2		= RDF::Trine::Statement->new( $b, $rdf->type, $foaf->Person );
30my $st3		= RDF::Trine::Statement->new( $b, $foaf->name, RDF::Trine::Node::Literal->new('Eve') );
31my $st4		= RDF::Trine::Statement->new( $p, $foaf->knows, $b );
32my $st5		= RDF::Trine::Statement->new( $p, $foaf->nick, $langval );
33my $st6		= RDF::Trine::Statement->new( $p, $foaf->age, $intval);
34
35my ($stores, $remove)	= stores();
36
37plan tests => 7 + 85 * scalar(@$stores);
38
39print "### Testing auto-creation of store\n";
40isa_ok( RDF::Trine::Model->new( 'Memory' ), 'RDF::Trine::Model' );
41
42foreach my $store (@$stores) {
43	print "### Testing store " . ref($store) . "\n";
44	isa_ok( $store, 'RDF::Trine::Store' );
45	my $model	= RDF::Trine::Model->new( $store );
46	isa_ok( $model, 'RDF::Trine::Model' );
47	$model->add_statement( $_ ) for ($st0, $st1, $st2, $st3);
48
49	{
50		is( $model->count_statements(), 4, 'model size' );
51		$model->add_statement( $_ ) for ($st0);
52		is( $model->count_statements(), 4, 'model size after duplicate statements' );
53		is( $model->count_statements( undef, $foaf->name, undef ), 2, 'count of foaf:name statements' );
54	}
55
56	{
57		my $stream	= $model->get_statements( $p, $foaf->name, RDF::Trine::Node::Variable->new('name') );
58		my $st		= $stream->next;
59		is_deeply( $st, $st1, 'foaf:name statement' );
60		is( $stream->next, undef, 'end-of-stream' );
61	}
62
63	{
64		throws_ok {
65			my $iter	= $model->get_statements('<foo>');
66		} 'RDF::Trine::Error::MethodInvocationError', 'get_statements called with non-object argument';
67
68	}
69
70	{
71		throws_ok {
72			$model->add_statement($p, $rdf->type, $foaf->Person);
73		} 'RDF::Trine::Error::MethodInvocationError', 'add_statement called with 3 nodes, not a statement';
74		throws_ok {
75			$model->add_statement($p, $rdf->type, $foaf->Person, $p);
76		} 'RDF::Trine::Error::MethodInvocationError', 'add_statement called with 4 nodes, not a statement';
77		throws_ok {
78			$model->add_statement('http://example.org/subject', 'http://example.org/predicate', 'String');
79		} 'RDF::Trine::Error::MethodInvocationError', 'add_statement called with strings, not a statement';
80	}
81
82
83	{
84		my $stream	= $model->get_statements( $b, $foaf->name, RDF::Trine::Node::Variable->new('name') );
85		my $st		= $stream->next;
86		is_deeply( $st, $st3, 'foaf:name statement (with bnode in triple)' );
87		is( $stream->next, undef, 'end-of-stream' );
88	}
89
90	{
91		my $stream	= $model->get_statements( RDF::Trine::Node::Variable->new('p'), $foaf->name, RDF::Trine::Node::Literal->new('Gregory Todd Williams') );
92		my $st		= $stream->next;
93		is_deeply( $st, $st1, 'foaf:name statement (with literal in triple)' );
94		is( $stream->next, undef, 'end-of-stream' );
95	}
96
97	{
98		my $stream	= $model->get_statements( RDF::Trine::Node::Variable->new('p'), $foaf->name, RDF::Trine::Node::Variable->new('name') );
99		my $count	= 0;
100		while (my $st = $stream->next) {
101			my $subj	= $st->subject;
102			isa_ok( $subj, 'RDF::Trine::Node' );
103			$count++;
104		}
105		is( $count, 2, 'expected result count (2 people) 1' );
106	}
107
108	{
109		my $p1		= RDF::Trine::Statement->new( RDF::Trine::Node::Variable->new('p'), $rdf->type, $foaf->Person );
110		my $p2		= RDF::Trine::Statement->new( RDF::Trine::Node::Variable->new('p'), $foaf->name, RDF::Trine::Node::Variable->new('name') );
111		my $pattern	= RDF::Trine::Pattern->new( $p1, $p2 );
112
113		{
114			my $stream	= $model->get_pattern( $pattern );
115			my $count	= 0;
116			while (my $b = $stream->next) {
117				isa_ok( $b, 'HASH' );
118				isa_ok( $b->{p}, 'RDF::Trine::Node', 'node person' );
119				isa_ok( $b->{name}, 'RDF::Trine::Node::Literal', 'literal name' );
120				like( $b->{name}->literal_value, qr/Eve|Gregory/, 'name pattern' );
121				$count++;
122			}
123			is( $count, 2, 'expected result count (2 people) 2' );
124		}
125
126		{
127			my $stream	= $model->get_pattern( $pattern, undef, orderby => [ 'name', 'ASC' ] );
128			is_deeply( [ $stream->sorted_by ], ['name', 'ASC'], 'results sort order' );
129			my $count	= 0;
130			my @expect	= ('Eve', 'Gregory Todd Williams');
131			while (my $b = $stream->next) {
132				isa_ok( $b, 'HASH' );
133				isa_ok( $b->{p}, 'RDF::Trine::Node', 'node person' );
134				my $name	= shift(@expect);
135				is( $b->{name}->literal_value, $name, 'name pattern' );
136				$count++;
137			}
138			is( $count, 2, 'expected result count (2 people) 3' );
139		}
140
141		{
142			my $stream	= $model->get_pattern( $pattern, undef, orderby => [ qw(name DESC p ASC) ] );
143			is_deeply( [ $stream->sorted_by ], ['name', 'DESC', 'p', 'ASC'], 'results sort order' );
144			my $count	= 0;
145			my @expect	= ('Gregory Todd Williams', 'Eve');
146			while (my $b = $stream->next) {
147				isa_ok( $b, 'HASH' );
148				isa_ok( $b->{p}, 'RDF::Trine::Node', 'node person' );
149				my $name	= shift(@expect);
150				is( $b->{name}->literal_value, $name, 'name pattern' );
151				$count++;
152			}
153			is( $count, 2, 'expected result count (2 people) 4' );
154		}
155
156		{
157			my $stream	= $model->get_pattern( $pattern, undef, orderby => [ 'date', 'ASC' ] );
158			is_deeply( [ $stream->sorted_by ], [], 'results sort order for unknown binding' );
159		}
160
161		{
162			throws_ok {
163				my $stream	= $model->get_pattern( $pattern, undef, orderby => [ 'name' ] );
164			} 'RDF::Trine::Error::MethodInvocationError', 'bad ordering request throws exception';
165		}
166	}
167
168	{
169		my $stream	= $model->get_pattern( $st0 );
170		my $empty	= $stream->next;
171		is_deeply( $empty, RDF::Trine::VariableBindings->new({}), 'empty binding on no-variable pattern' );
172		is( $stream->next, undef, 'end-of-stream' );
173	}
174
175	{
176		my $stream	= $model->as_stream();
177		isa_ok( $stream, 'RDF::Trine::Iterator::Graph' );
178		my $count	= 0;
179		while (my $st = $stream->next) {
180			my $p	= $st->predicate;
181			like( $p->uri_value, qr<(#type|/name)$>, 'as_stream statement' );
182			$count++;
183		}
184		is( $count, 4, 'expected model statement count (4)' );
185	}
186
187	{
188		{
189			my @subj	= $model->subjects( $rdf->type );
190			my @preds	= $model->predicates( $p );
191			my @objs	= $model->objects( $p );
192			is( scalar(@subj), 2, "expected subject count on rdf:type" );
193			is( scalar(@preds), 2, "expected predicate count on " . $p->uri_value );
194			is( scalar(@objs), 2, "expected objects count on " . $p->uri_value );
195		}
196		{
197			my @subjs	= $model->subjects( $foaf->name, literal('Eve') );
198			my @preds	= $model->predicates( $p, $foaf->Person );
199			my @objs	= $model->objects( $p, $rdf->type );
200			is( scalar(@subjs), 1, "expected subject count on rdf:type" );
201			ok( $subjs[0]->isa('RDF::Trine::Node::Blank'), 'expected subject' );
202			is( scalar(@preds), 1, "expected predicate count on " . $p->uri_value );
203			ok( $preds[0]->equal( $rdf->type ), 'expected predicate' );
204			is( scalar(@objs), 1, "expected objects count on " . $p->uri_value );
205			ok( $objs[0]->equal( $foaf->Person ), 'expected object' );
206		}
207		{
208			my $subjs	= $model->subjects( $rdf->type );
209			my $preds	= $model->predicates( $p );
210			my $objs	= $model->objects( $p );
211			isa_ok( $subjs, 'RDF::Trine::Iterator', 'expected iterator from subjects()' );
212			isa_ok( $preds, 'RDF::Trine::Iterator', 'expected iterator from predicates()' );
213			isa_ok( $objs, 'RDF::Trine::Iterator', 'expected iterator from objects()' );
214		}
215	}
216
217	{
218		my $st5		= RDF::Trine::Statement->new( $p, $foaf->name, RDF::Trine::Node::Literal->new('グレゴリ ウィリアムス', 'jp') );
219		$model->add_statement( $st5 );
220
221		my $pattern	= RDF::Trine::Statement->new( $p, $foaf->name, RDF::Trine::Node::Variable->new('name') );
222		my $stream	= $model->get_pattern( $pattern );
223		my $count	= 0;
224		while (my $b = $stream->next) {
225			isa_ok( $b, 'HASH' );
226			isa_ok( $b->{name}, 'RDF::Trine::Node::Literal', 'literal name' );
227			my $value	= $b->{name}->literal_value;
228			like( $value, qr/Gregory|グレゴリ/, 'name pattern with language-tagged result' );
229			$count++;
230		}
231		is( $count, 2, 'expected result count (2 names)' );
232		is( $model->count_statements(), 5, 'model size' );
233		$model->remove_statement( $st5 );
234		is( $model->count_statements(), 4, 'model size after remove_statement' );
235	}
236
237	{
238		my $st6		= RDF::Trine::Statement->new( $p, $foaf->name, RDF::Trine::Node::Literal->new('Gregory Todd Williams', undef, 'http://www.w3.org/2000/01/rdf-schema#Literal') );
239		$model->add_statement( $st6 );
240
241		my $pattern	= RDF::Trine::Statement->new( $p, $foaf->name, RDF::Trine::Node::Variable->new('name') );
242		my $stream	= $model->get_pattern( $pattern );
243		my $count	= 0;
244		my $dt		= 0;
245		while (my $b = $stream->next) {
246			my $name	= $b->{name};
247			isa_ok( $b, 'HASH' );
248			isa_ok( $name, 'RDF::Trine::Node::Literal', 'literal name' );
249			is( $name->literal_value, 'Gregory Todd Williams', 'name pattern with datatyped result' );
250			if (my $type = $name->literal_datatype) {
251				is( $type, 'http://www.w3.org/2000/01/rdf-schema#Literal', 'datatyped literal' );
252				$dt++;
253			}
254			$count++;
255		}
256		is( $count, 2, 'expected result count (2 names)' );
257		is( $dt, 1, 'expected result count (1 datatyped literal)' );
258	}
259
260	{
261		$model->remove_statements( $p );
262		is( $model->count_statements(), 2, 'model size after remove_statements' );
263	}
264
265	{
266		throws_ok {
267			my $pattern	= RDF::Trine::Pattern->new();
268			my $stream	= $model->get_pattern( $pattern );
269		} 'RDF::Trine::Error::CompilationError', 'empty GGP throws exception';
270	}
271}
272
273foreach my $file (@$remove) {
274	unlink( $file );
275}
276
277{ # test optional parameters of RDF::Trine::Model::objects
278	my $model = RDF::Trine::Model->new;
279	$model->add_statement( $_ ) for ($st0, $st1, $st3, $st4, $st5, $st6);
280	my %types = (blank => 1, literal => 3, resource => 1);
281	while (my ($type,$count) = each(%types)) {
282		my @objs	= $model->objects( $p, undef, type => $type );
283		is( scalar(@objs), $count, "expected objects count on type $type");
284	}
285	my @objs	= $model->objects( $p, undef, language => 'en' );
286	ok( $objs[0]->equal( $langval ), 'expected integer value as object' );
287	foreach my $dt ( $xsd->int, $xsd->int->uri_value ) {
288		@objs	= $model->objects( $p, undef, datatype => $dt );
289		ok( $objs[0]->equal( $intval ), 'expected integer value as object' );
290	}
291}
292
293
294sub stores {
295	my @stores;
296	my @removeme;
297	push(@stores, RDF::Trine::Store::Memory->temporary_store());
298
299	{
300		my $store	= RDF::Trine::Store::DBI->new();
301		$store->init();
302		push(@stores, $store);
303	}
304
305	{
306		my ($fh, $filename) = tempfile();
307		undef $fh;
308		my $dbh		= DBI->connect( "dbi:SQLite:dbname=${filename}", '', '' );
309		my $store	= RDF::Trine::Store::DBI->new( 'model', $dbh );
310		$store->init();
311		push(@stores, $store);
312		push(@removeme, $filename);
313	}
314
315	{
316		my ($fh, $filename) = tempfile();
317		undef $fh;
318		my $dsn		= "dbi:SQLite:dbname=${filename}";
319		my $store	= RDF::Trine::Store::DBI->new( 'model', $dsn, '', '' );
320		$store->init();
321		push(@stores, $store);
322		push(@removeme, $filename);
323	}
324	return (\@stores, \@removeme);
325}
326
327sub debug {
328	my $store	= shift;
329	my $dbh		= $store->dbh;
330	my $sth		= $dbh->prepare( "SELECT * FROM Statements15799945864759145248" );
331	$sth->execute();
332	while (my $row = $sth->fetchrow_hashref) {
333		warn Dumper($row);
334	}
335}
336