Bug 28202: Unit test
[koha.git] / t / db_dependent / Koha / Biblio.t
1 #!/usr/bin/perl
2
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 # Koha is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18 use Modern::Perl;
19
20 use Test::More tests => 14;
21
22 use C4::Biblio;
23 use Koha::Database;
24 use Koha::Acquisition::Orders;
25
26 use t::lib::TestBuilder;
27 use t::lib::Mocks;
28
29 BEGIN {
30     use_ok('Koha::Biblio');
31     use_ok('Koha::Biblios');
32 }
33
34 my $schema  = Koha::Database->new->schema;
35 my $builder = t::lib::TestBuilder->new;
36
37 subtest 'metadata() tests' => sub {
38
39     plan tests => 4;
40
41     $schema->storage->txn_begin;
42
43     my $title = 'Oranges and Peaches';
44
45     my $record = MARC::Record->new();
46     my $field = MARC::Field->new('245','','','a' => $title);
47     $record->append_fields( $field );
48     my ($biblionumber) = C4::Biblio::AddBiblio($record, '');
49
50     my $biblio = Koha::Biblios->find( $biblionumber );
51     is( ref $biblio, 'Koha::Biblio', 'Found a Koha::Biblio object' );
52
53     my $metadata = $biblio->metadata;
54     is( ref $metadata, 'Koha::Biblio::Metadata', 'Method metadata() returned a Koha::Biblio::Metadata object' );
55
56     my $record2 = $metadata->record;
57     is( ref $record2, 'MARC::Record', 'Method record() returned a MARC::Record object' );
58
59     is( $record2->field('245')->subfield("a"), $title, 'Title in 245$a matches title from original record object' );
60
61     $schema->storage->txn_rollback;
62 };
63
64 subtest 'hidden_in_opac() tests' => sub {
65
66     plan tests => 4;
67
68     $schema->storage->txn_begin;
69
70     my $biblio = $builder->build_sample_biblio();
71
72     ok( !$biblio->hidden_in_opac({ rules => { withdrawn => [ 2 ] } }), 'Biblio not hidden if there is no item attached' );
73
74     my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
75     my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
76
77     $item_1->withdrawn( 1 )->store->discard_changes;
78     $item_2->withdrawn( 1 )->store->discard_changes;
79
80     ok( !$biblio->hidden_in_opac({ rules => { withdrawn => [ 2 ] } }), 'Biblio not hidden' );
81
82     $item_2->withdrawn( 2 )->store->discard_changes;
83     $biblio->discard_changes; # refresh
84
85     ok( !$biblio->hidden_in_opac({ rules => { withdrawn => [ 2 ] } }), 'Biblio not hidden' );
86
87     $item_1->withdrawn( 2 )->store->discard_changes;
88     $biblio->discard_changes; # refresh
89
90     ok( $biblio->hidden_in_opac({ rules => { withdrawn => [ 2 ] } }), 'Biblio hidden' );
91
92     $schema->storage->txn_rollback;
93 };
94
95 subtest 'items() tests' => sub {
96
97     plan tests => 4;
98
99     $schema->storage->txn_begin;
100
101     my $biblio = $builder->build_sample_biblio();
102
103     is( $biblio->items->count, 0, 'No items, count is 0' );
104
105     my $item_1 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
106     my $item_2 = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
107
108     my $items = $biblio->items;
109     is( ref($items), 'Koha::Items', 'Returns a Koha::Items resultset' );
110     is( $items->count, 2, 'Two items in resultset' );
111
112     my @items = $biblio->items->as_list;
113     is( scalar @items, 2, 'Same result, but in list context' );
114
115     $schema->storage->txn_rollback;
116
117 };
118
119 subtest 'get_coins and get_openurl' => sub {
120
121     plan tests => 4;
122
123     $schema->storage->txn_begin;
124
125     my $builder = t::lib::TestBuilder->new;
126     my $biblio = $builder->build_sample_biblio({
127             title => 'Title 1',
128             author => 'Author 1'
129         });
130     is(
131         $biblio->get_coins,
132         'ctx_ver=Z39.88-2004&amp;rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&amp;rft.genre=book&amp;rft.btitle=Title%201&amp;rft.au=Author%201',
133         'GetCOinsBiblio returned right metadata'
134     );
135
136     my $record = MARC::Record->new();
137     $record->append_fields( MARC::Field->new('100','','','a' => 'Author 2'), MARC::Field->new('880','','','a' => 'Something') );
138     my ( $biblionumber ) = C4::Biblio::AddBiblio($record, '');
139     my $biblio_no_title = Koha::Biblios->find($biblionumber);
140     is(
141         $biblio_no_title->get_coins,
142         'ctx_ver=Z39.88-2004&amp;rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&amp;rft.genre=book&amp;rft.au=Author%202',
143         'GetCOinsBiblio returned right metadata if biblio does not have a title'
144     );
145
146     t::lib::Mocks::mock_preference("OpenURLResolverURL", "https://koha.example.com/");
147     is(
148         $biblio->get_openurl,
149         'https://koha.example.com/?ctx_ver=Z39.88-2004&amp;rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&amp;rft.genre=book&amp;rft.btitle=Title%201&amp;rft.au=Author%201',
150         'Koha::Biblio->get_openurl returned right URL'
151     );
152
153     t::lib::Mocks::mock_preference("OpenURLResolverURL", "https://koha.example.com/?client_id=ci1");
154     is(
155         $biblio->get_openurl,
156         'https://koha.example.com/?client_id=ci1&amp;ctx_ver=Z39.88-2004&amp;rft_val_fmt=info%3Aofi%2Ffmt%3Akev%3Amtx%3Abook&amp;rft.genre=book&amp;rft.btitle=Title%201&amp;rft.au=Author%201',
157         'Koha::Biblio->get_openurl returned right URL'
158     );
159
160     $schema->storage->txn_rollback;
161 };
162
163 subtest 'is_serial() tests' => sub {
164
165     plan tests => 3;
166
167     $schema->storage->txn_begin;
168
169     my $biblio = $builder->build_sample_biblio();
170
171     $biblio->serial( 1 )->store->discard_changes;
172     ok( $biblio->is_serial, 'Bibliographic record is serial' );
173
174     $biblio->serial( 0 )->store->discard_changes;
175     ok( !$biblio->is_serial, 'Bibliographic record is not serial' );
176
177     my $record = $biblio->metadata->record;
178     $record->leader('00142nas a22     7a 4500');
179     ModBiblio($record, $biblio->biblionumber );
180     $biblio = Koha::Biblios->find($biblio->biblionumber);
181
182     ok( $biblio->is_serial, 'Bibliographic record is serial' );
183
184     $schema->storage->txn_rollback;
185 };
186
187 subtest 'pickup_locations' => sub {
188     plan tests => 9;
189
190     $schema->storage->txn_begin;
191
192     Koha::CirculationRules->search->delete;
193     Koha::CirculationRules->set_rules(
194         {
195             categorycode => undef,
196             itemtype     => undef,
197             branchcode   => undef,
198             rules        => {
199                 reservesallowed => 25,
200             }
201         }
202     );
203
204     my $root1 = $builder->build_object( { class => 'Koha::Library::Groups', value => { ft_local_hold_group => 1 } } );
205     my $root2 = $builder->build_object( { class => 'Koha::Library::Groups', value => { ft_local_hold_group => 1 } } );
206     my $root3 = $builder->build_object( { class => 'Koha::Library::Groups', value => { ft_local_hold_group => 1 } } );
207
208     my $library1 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'zzz' } } );
209     my $library2 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'AAA' } } );
210     my $library3 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 0, branchname => 'FFF' } } );
211     my $library4 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'CCC' } } );
212     my $library5 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'eee' } } );
213     my $library6 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'BBB' } } );
214     my $library7 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 1, branchname => 'DDD' } } );
215     my $library8 = $builder->build_object( { class => 'Koha::Libraries', value => { pickup_location => 0, branchname => 'GGG' } } );
216
217     our @branchcodes = map { $_->branchcode } ($library1, $library2, $library3, $library4, $library5, $library6, $library7, $library8);
218
219     Koha::CirculationRules->set_rules(
220         {
221             branchcode => $library1->branchcode,
222             itemtype   => undef,
223             rules => {
224                 holdallowed => 'from_home_library',
225                 hold_fulfillment_policy => 'any',
226                 returnbranch => 'any'
227             }
228         }
229     );
230
231     Koha::CirculationRules->set_rules(
232         {
233             branchcode => $library2->branchcode,
234             itemtype   => undef,
235             rules => {
236                 holdallowed => 'from_local_hold_group',
237                 hold_fulfillment_policy => 'holdgroup',
238                 returnbranch => 'any'
239             }
240         }
241     );
242
243     Koha::CirculationRules->set_rules(
244         {
245             branchcode => $library3->branchcode,
246             itemtype   => undef,
247             rules => {
248                 holdallowed => 'from_local_hold_group',
249                 hold_fulfillment_policy => 'patrongroup',
250                 returnbranch => 'any'
251             }
252         }
253     );
254
255     Koha::CirculationRules->set_rules(
256         {
257             branchcode => $library4->branchcode,
258             itemtype   => undef,
259             rules => {
260                 holdallowed => 'from_any_library',
261                 hold_fulfillment_policy => 'holdingbranch',
262                 returnbranch => 'any'
263             }
264         }
265     );
266
267     Koha::CirculationRules->set_rules(
268         {
269             branchcode => $library5->branchcode,
270             itemtype   => undef,
271             rules => {
272                 holdallowed => 'from_any_library',
273                 hold_fulfillment_policy => 'homebranch',
274                 returnbranch => 'any'
275             }
276         }
277     );
278
279     Koha::CirculationRules->set_rules(
280         {
281             branchcode => $library6->branchcode,
282             itemtype   => undef,
283             rules => {
284                 holdallowed => 'from_home_library',
285                 hold_fulfillment_policy => 'holdgroup',
286                 returnbranch => 'any'
287             }
288         }
289     );
290
291     Koha::CirculationRules->set_rules(
292         {
293             branchcode => $library7->branchcode,
294             itemtype   => undef,
295             rules => {
296                 holdallowed => 'from_local_hold_group',
297                 hold_fulfillment_policy => 'holdingbranch',
298                 returnbranch => 'any'
299             }
300         }
301     );
302
303
304     Koha::CirculationRules->set_rules(
305         {
306             branchcode => $library8->branchcode,
307             itemtype   => undef,
308             rules => {
309                 holdallowed => 'from_any_library',
310                 hold_fulfillment_policy => 'patrongroup',
311                 returnbranch => 'any'
312             }
313         }
314     );
315
316     my $group1_1 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root1->id, branchcode => $library1->branchcode } } );
317     my $group1_2 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root1->id, branchcode => $library2->branchcode } } );
318
319     my $group2_3 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root2->id, branchcode => $library3->branchcode } } );
320     my $group2_4 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root2->id, branchcode => $library4->branchcode } } );
321
322     my $group3_5 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library5->branchcode } } );
323     my $group3_6 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library6->branchcode } } );
324     my $group3_7 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library7->branchcode } } );
325     my $group3_8 = $builder->build_object( { class => 'Koha::Library::Groups', value => { parent_id => $root3->id, branchcode => $library8->branchcode } } );
326
327     my $biblio1  = $builder->build_sample_biblio({ title => '1' });
328     my $biblio2  = $builder->build_sample_biblio({ title => '2' });
329
330     my $item1_1  = $builder->build_sample_item({
331         biblionumber     => $biblio1->biblionumber,
332         homebranch       => $library1->branchcode,
333         holdingbranch    => $library2->branchcode,
334     })->store;
335
336     my $item1_3  = $builder->build_sample_item({
337         biblionumber     => $biblio1->biblionumber,
338         homebranch       => $library3->branchcode,
339         holdingbranch    => $library4->branchcode,
340     })->store;
341
342     my $item1_7  = $builder->build_sample_item({
343         biblionumber     => $biblio1->biblionumber,
344         homebranch       => $library7->branchcode,
345         holdingbranch    => $library4->branchcode,
346     })->store;
347
348     my $item2_2  = $builder->build_sample_item({
349         biblionumber     => $biblio2->biblionumber,
350         homebranch       => $library2->branchcode,
351         holdingbranch    => $library1->branchcode,
352     })->store;
353
354     my $item2_4  = $builder->build_sample_item({
355         biblionumber     => $biblio2->biblionumber,
356         homebranch       => $library4->branchcode,
357         holdingbranch    => $library3->branchcode,
358     })->store;
359
360     my $item2_6  = $builder->build_sample_item({
361         biblionumber     => $biblio2->biblionumber,
362         homebranch       => $library6->branchcode,
363         holdingbranch    => $library4->branchcode,
364     })->store;
365
366     my $patron1 = $builder->build_object( { class => 'Koha::Patrons', value => { firstname=>'1', branchcode => $library1->branchcode } } );
367     my $patron8 = $builder->build_object( { class => 'Koha::Patrons', value => { firstname=>'8', branchcode => $library8->branchcode } } );
368
369     my $results = {
370         "ItemHomeLibrary-1-1" => 6,
371         "ItemHomeLibrary-1-8" => 1,
372         "ItemHomeLibrary-2-1" => 2,
373         "ItemHomeLibrary-2-8" => 0,
374         "PatronLibrary-1-1" => 6,
375         "PatronLibrary-1-8" => 3,
376         "PatronLibrary-2-1" => 0,
377         "PatronLibrary-2-8" => 3,
378     };
379
380     sub _doTest {
381         my ( $cbranch, $biblio, $patron, $results ) = @_;
382         t::lib::Mocks::mock_preference('ReservesControlBranch', $cbranch);
383
384         my @pl = map {
385             my $pickup_location = $_;
386             grep { $pickup_location->branchcode eq $_ } @branchcodes
387         } $biblio->pickup_locations( { patron => $patron } )->as_list;
388
389         ok(
390             scalar(@pl) == $results->{ $cbranch . '-'
391                   . $biblio->title . '-'
392                   . $patron->firstname },
393             'ReservesControlBranch: '
394               . $cbranch
395               . ', biblio'
396               . $biblio->title
397               . ', patron'
398               . $patron->firstname
399               . ' should return '
400               . $results->{ $cbranch . '-'
401                   . $biblio->title . '-'
402                   . $patron->firstname }
403               . ' but returns '
404               . scalar(@pl)
405         );
406     }
407
408     foreach my $cbranch ('ItemHomeLibrary','PatronLibrary') {
409         foreach my $biblio ($biblio1, $biblio2) {
410             foreach my $patron ($patron1, $patron8) {
411                 _doTest($cbranch, $biblio, $patron, $results);
412             }
413         }
414     }
415
416     my @pl_names = map { $_->branchname } $biblio1->pickup_locations( { patron => $patron1 } )->as_list;
417     my $pl_ori_str = join('|', @pl_names);
418     my $pl_sorted_str = join('|', sort { lc($a) cmp lc($b) } @pl_names);
419     ok(
420         $pl_ori_str eq $pl_sorted_str,
421         'Libraries must be sorted by name'
422     );
423     $schema->storage->txn_rollback;
424 };
425
426 subtest 'to_api() tests' => sub {
427
428     $schema->storage->txn_begin;
429
430     my $biblio = $builder->build_sample_biblio();
431     my $item = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
432
433     my $biblioitem_api = $biblio->biblioitem->to_api;
434     my $biblio_api     = $biblio->to_api;
435
436     plan tests => (scalar keys %{ $biblioitem_api }) + 1;
437
438     foreach my $key ( keys %{ $biblioitem_api } ) {
439         is( $biblio_api->{$key}, $biblioitem_api->{$key}, "$key is added to the biblio object" );
440     }
441
442     $biblio_api = $biblio->to_api({ embed => { items => {} } });
443     is_deeply( $biblio_api->{items}, [ $item->to_api ], 'Item correctly embedded' );
444
445     $schema->storage->txn_rollback;
446 };
447
448 subtest 'suggestions() tests' => sub {
449
450     plan tests => 3;
451
452     $schema->storage->txn_begin;
453
454     my $biblio     = $builder->build_sample_biblio();
455
456     is( ref($biblio->suggestions), 'Koha::Suggestions', 'Return type is correct' );
457
458     is_deeply(
459         $biblio->suggestions->unblessed,
460         [],
461         '->suggestions returns an empty Koha::Suggestions resultset'
462     );
463
464     my $suggestion = $builder->build_object(
465         {
466             class => 'Koha::Suggestions',
467             value => { biblionumber => $biblio->biblionumber }
468         }
469     );
470
471     my $suggestions = $biblio->suggestions->unblessed;
472
473     is_deeply(
474         $biblio->suggestions->unblessed,
475         [ $suggestion->unblessed ],
476         '->suggestions returns the related Koha::Suggestion objects'
477     );
478
479     $schema->storage->txn_rollback;
480 };
481
482 subtest 'orders() and active_orders() tests' => sub {
483
484     plan tests => 5;
485
486     $schema->storage->txn_begin;
487
488     my $biblio = $builder->build_sample_biblio();
489
490     my $orders        = $biblio->orders;
491     my $active_orders = $biblio->active_orders;
492
493     is( ref($orders), 'Koha::Acquisition::Orders', 'Result type is correct' );
494     is( $biblio->orders->count, $biblio->active_orders->count, '->orders->count returns the count for the resultset' );
495
496     # Add a couple orders
497     foreach (1..2) {
498         $builder->build_object(
499             {
500                 class => 'Koha::Acquisition::Orders',
501                 value => {
502                     biblionumber => $biblio->biblionumber,
503                     datecancellationprinted => '2019-12-31'
504                 }
505             }
506         );
507     }
508
509     $builder->build_object(
510         {
511             class => 'Koha::Acquisition::Orders',
512             value => {
513                 biblionumber => $biblio->biblionumber,
514                 datecancellationprinted => undef
515             }
516         }
517     );
518
519     $orders = $biblio->orders;
520     $active_orders = $biblio->active_orders;
521
522     is( ref($orders), 'Koha::Acquisition::Orders', 'Result type is correct' );
523     is( ref($active_orders), 'Koha::Acquisition::Orders', 'Result type is correct' );
524     is( $orders->count, $active_orders->count + 2, '->active_orders->count returns the rigt count' );
525
526     $schema->storage->txn_rollback;
527 };
528
529 subtest 'subscriptions() tests' => sub {
530
531     plan tests => 4;
532
533     $schema->storage->txn_begin;
534
535     my $biblio = $builder->build_sample_biblio;
536
537     my $subscriptions = $biblio->subscriptions;
538     is( ref($subscriptions), 'Koha::Subscriptions',
539         'Koha::Biblio->subscriptions should return a Koha::Subscriptions object'
540     );
541     is( $subscriptions->count, 0, 'Koha::Biblio->subscriptions should return the correct number of subscriptions');
542
543     # Add two subscriptions
544     foreach (1..2) {
545         $builder->build_object(
546             {
547                 class => 'Koha::Subscriptions',
548                 value => { biblionumber => $biblio->biblionumber }
549             }
550         );
551     }
552
553     $subscriptions = $biblio->subscriptions;
554     is( ref($subscriptions), 'Koha::Subscriptions',
555         'Koha::Biblio->subscriptions should return a Koha::Subscriptions object'
556     );
557     is( $subscriptions->count, 2, 'Koha::Biblio->subscriptions should return the correct number of subscriptions');
558
559     $schema->storage->txn_rollback;
560 };
561
562 subtest 'get_marc_notes() MARC21 tests' => sub {
563     plan tests => 11;
564
565     $schema->storage->txn_begin;
566
567     t::lib::Mocks::mock_preference( 'NotesToHide', '520' );
568
569     my $biblio = $builder->build_sample_biblio;
570     my $record = $biblio->metadata->record;
571     $record->append_fields(
572         MARC::Field->new( '500', '', '', a => 'Note1' ),
573         MARC::Field->new( '505', '', '', a => 'Note2', u => 'http://someserver.com' ),
574         MARC::Field->new( '520', '', '', a => 'Note3 skipped' ),
575         MARC::Field->new( '541', '0', '', a => 'Note4 skipped on opac' ),
576         MARC::Field->new( '541', '', '', a => 'Note5' ),
577     );
578     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
579     $biblio = Koha::Biblios->find( $biblio->biblionumber);
580     my $notes = $biblio->get_marc_notes({ marcflavour => 'MARC21' });
581     is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
582     is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
583     is( $notes->[2]->{marcnote}, 'http://someserver.com', 'URL separated' );
584     is( $notes->[3]->{marcnote}, 'Note4 skipped on opac',"Not shows if not opac" );
585     is( $notes->[4]->{marcnote}, 'Note5', 'Fifth note' );
586     is( @$notes, 5, 'No more notes' );
587     $notes = $biblio->get_marc_notes({ marcflavour => 'MARC21', opac => 1 });
588     is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
589     is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
590     is( $notes->[2]->{marcnote}, 'http://someserver.com', 'URL separated' );
591     is( $notes->[3]->{marcnote}, 'Note5', 'Fifth note shows after fourth skipped' );
592     is( @$notes, 4, 'No more notes' );
593
594     $schema->storage->txn_rollback;
595 };
596
597 subtest 'get_marc_notes() UNIMARC tests' => sub {
598     plan tests => 3;
599
600     $schema->storage->txn_begin;
601
602     t::lib::Mocks::mock_preference( 'NotesToHide', '310' );
603
604     my $biblio = $builder->build_sample_biblio;
605     my $record = $biblio->metadata->record;
606     $record->append_fields(
607         MARC::Field->new( '300', '', '', a => 'Note1' ),
608         MARC::Field->new( '300', '', '', a => 'Note2' ),
609         MARC::Field->new( '310', '', '', a => 'Note3 skipped' ),
610     );
611     C4::Biblio::ModBiblio( $record, $biblio->biblionumber );
612     $biblio = Koha::Biblios->find( $biblio->biblionumber);
613     my $notes = $biblio->get_marc_notes({ marcflavour => 'UNIMARC' });
614     is( $notes->[0]->{marcnote}, 'Note1', 'First note' );
615     is( $notes->[1]->{marcnote}, 'Note2', 'Second note' );
616     is( @$notes, 2, 'No more notes' );
617
618     $schema->storage->txn_rollback;
619 };