| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
# Copyright (c) 2024-2025 Philipp Schafft |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# licensed under Artistic License 2.0 (see LICENSE file) |
|
4
|
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
# ABSTRACT: Work with Tag databases |
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
package Data::TagDB; |
|
8
|
|
|
|
|
|
|
|
|
9
|
1
|
|
|
1
|
|
314494
|
use v5.10; |
|
|
1
|
|
|
|
|
3
|
|
|
10
|
1
|
|
|
1
|
|
5
|
use strict; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
23
|
|
|
11
|
1
|
|
|
1
|
|
3
|
use warnings; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
48
|
|
|
12
|
|
|
|
|
|
|
|
|
13
|
1
|
|
|
1
|
|
3
|
use Scalar::Util qw(weaken blessed); |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
58
|
|
|
14
|
|
|
|
|
|
|
|
|
15
|
1
|
|
|
1
|
|
4
|
use Carp; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
55
|
|
|
16
|
1
|
|
|
1
|
|
2113
|
use DBI; |
|
|
1
|
|
|
|
|
15166
|
|
|
|
1
|
|
|
|
|
65
|
|
|
17
|
|
|
|
|
|
|
|
|
18
|
1
|
|
|
1
|
|
550
|
use Data::TagDB::Tag; |
|
|
1
|
|
|
|
|
7
|
|
|
|
1
|
|
|
|
|
57
|
|
|
19
|
1
|
|
|
1
|
|
647
|
use Data::TagDB::Relation; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
46
|
|
|
20
|
1
|
|
|
1
|
|
540
|
use Data::TagDB::Metadata; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
76
|
|
|
21
|
1
|
|
|
1
|
|
529
|
use Data::TagDB::LinkIterator; |
|
|
1
|
|
|
|
|
4
|
|
|
|
1
|
|
|
|
|
44
|
|
|
22
|
1
|
|
|
1
|
|
620
|
use Data::TagDB::MultiIterator; |
|
|
1
|
|
|
|
|
4
|
|
|
|
1
|
|
|
|
|
126
|
|
|
23
|
1
|
|
|
1
|
|
669
|
use Data::TagDB::WellKnown; |
|
|
1
|
|
|
|
|
4
|
|
|
|
1
|
|
|
|
|
52
|
|
|
24
|
1
|
|
|
1
|
|
637
|
use Data::TagDB::Cloudlet; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
43
|
|
|
25
|
1
|
|
|
1
|
|
882
|
use Data::URIID::Colour; |
|
|
1
|
|
|
|
|
3540
|
|
|
|
1
|
|
|
|
|
8742
|
|
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
our $VERSION = v0.12; |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
my %_queries = ( |
|
30
|
|
|
|
|
|
|
_default => { |
|
31
|
|
|
|
|
|
|
tag_by_hint => 'SELECT tag FROM hint WHERE name = ?', |
|
32
|
|
|
|
|
|
|
_tag_simple_identifier => 'SELECT data FROM metadata WHERE relation = (SELECT tag FROM hint WHERE name = \'also-shares-identifier\') AND type = (SELECT tag FROM hint WHERE name = ?) AND context = 0 AND encoding = 0 AND tag = ? ORDER BY data DESC', |
|
33
|
|
|
|
|
|
|
_tag_by_dbid_type_and_data => 'SELECT tag FROM metadata WHERE relation = (SELECT tag FROM hint WHERE name = \'also-shares-identifier\') AND type = ? AND context = 0 AND encoding = 0 AND data = ?', |
|
34
|
|
|
|
|
|
|
_create_tag => 'INSERT INTO tag DEFAULT VALUES', |
|
35
|
|
|
|
|
|
|
_create_metadata => 'INSERT OR IGNORE INTO metadata (tag,relation,context,type,encoding,data) VALUES (?,?,?,?,?,?)', |
|
36
|
|
|
|
|
|
|
_create_relation => 'INSERT OR IGNORE INTO relation (tag,relation,related,context,filter) VALUES (?,?,?,?,?)', |
|
37
|
|
|
|
|
|
|
}, |
|
38
|
|
|
|
|
|
|
Pg => { |
|
39
|
|
|
|
|
|
|
_create_tag => 'INSERT INTO tag DEFAULT VALUES RETURNING id', |
|
40
|
|
|
|
|
|
|
_create_metadata => 'INSERT INTO metadata (tag,relation,context,type,encoding,data) VALUES (?,?,?,?,?,?) ON CONFLICT DO NOTHING', |
|
41
|
|
|
|
|
|
|
_create_relation => 'INSERT INTO relation (tag,relation,related,context,filter) VALUES (?,?,?,?,?) ON CONFLICT DO NOTHING', |
|
42
|
|
|
|
|
|
|
}, |
|
43
|
|
|
|
|
|
|
); |
|
44
|
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
sub new { |
|
48
|
0
|
|
|
0
|
1
|
|
my ($pkg, $first, @rest) = @_; |
|
49
|
0
|
|
|
|
|
|
my $DBI_name; |
|
50
|
|
|
|
|
|
|
my $dbh; |
|
51
|
0
|
|
|
|
|
|
my %query; |
|
52
|
|
|
|
|
|
|
|
|
53
|
0
|
0
|
|
|
|
|
croak 'No dsn or dbh given to new' unless defined $first; |
|
54
|
|
|
|
|
|
|
|
|
55
|
0
|
0
|
0
|
|
|
|
if (scalar(@rest) == 0 && eval { $first->can('prepare'); }) { |
|
|
0
|
|
|
|
|
|
|
|
56
|
0
|
|
|
|
|
|
$dbh = $first; |
|
57
|
|
|
|
|
|
|
} else { |
|
58
|
0
|
0
|
|
|
|
|
$dbh = DBI->connect($first, @rest) or croak 'Cannot connect to database'; |
|
59
|
|
|
|
|
|
|
} |
|
60
|
|
|
|
|
|
|
|
|
61
|
0
|
|
|
|
|
|
$DBI_name = $dbh->{Driver}{Name}; |
|
62
|
0
|
|
|
|
|
|
foreach my $name (keys %{$_queries{_default}}) { |
|
|
0
|
|
|
|
|
|
|
|
63
|
0
|
|
0
|
|
|
|
$query{$name} = $dbh->prepare($_queries{$DBI_name}{$name} // $_queries{_default}{$name}); |
|
64
|
|
|
|
|
|
|
} |
|
65
|
|
|
|
|
|
|
|
|
66
|
0
|
|
|
|
|
|
return bless { |
|
67
|
|
|
|
|
|
|
dbh => $dbh, |
|
68
|
|
|
|
|
|
|
_DBI_name => $DBI_name, |
|
69
|
|
|
|
|
|
|
cache_tag => {}, |
|
70
|
|
|
|
|
|
|
cache_ise => {}, |
|
71
|
|
|
|
|
|
|
cache_default_type => {}, |
|
72
|
|
|
|
|
|
|
cache_default_encoding => {}, |
|
73
|
|
|
|
|
|
|
backup_type => {}, |
|
74
|
|
|
|
|
|
|
query => \%query, |
|
75
|
|
|
|
|
|
|
}, $pkg; |
|
76
|
|
|
|
|
|
|
} |
|
77
|
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
sub dbh { |
|
80
|
0
|
|
|
0
|
1
|
|
my ($self) = @_; |
|
81
|
0
|
|
|
|
|
|
return $self->{dbh}; |
|
82
|
|
|
|
|
|
|
} |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
sub disconnect { |
|
86
|
0
|
|
|
0
|
1
|
|
my ($self) = @_; |
|
87
|
0
|
|
|
|
|
|
$self->assert_connected->disconnect; |
|
88
|
0
|
|
|
|
|
|
$self->{dbh} = undef; |
|
89
|
0
|
|
|
|
|
|
$self->_cache_clear; |
|
90
|
|
|
|
|
|
|
} |
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
sub tag_by_id { |
|
94
|
0
|
|
|
0
|
1
|
|
my ($self, $type, $id, $autocreate) = @_; |
|
95
|
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
# Shift arguments into correct order as needed: |
|
97
|
0
|
0
|
0
|
|
|
|
if (blessed($type) && $type->isa('Data::Identifier')) { |
|
98
|
0
|
|
|
|
|
|
($self, $id, $autocreate) = @_; |
|
99
|
|
|
|
|
|
|
|
|
100
|
0
|
|
|
|
|
|
$type = $id->type; |
|
101
|
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
# Is $type === UUID? |
|
103
|
|
|
|
|
|
|
# TODO: Make this a better check. |
|
104
|
0
|
0
|
|
|
|
|
if ($type->eq('uuid')) { |
|
105
|
0
|
|
|
|
|
|
$type = $self->tag_by_hint('uuid'); |
|
106
|
|
|
|
|
|
|
} else { |
|
107
|
0
|
|
|
|
|
|
$type = $self->tag_by_id($type); |
|
108
|
|
|
|
|
|
|
} |
|
109
|
|
|
|
|
|
|
|
|
110
|
0
|
|
|
|
|
|
$id = $id->id; |
|
111
|
|
|
|
|
|
|
} |
|
112
|
|
|
|
|
|
|
|
|
113
|
0
|
0
|
|
|
|
|
$type = $self->tag_by_hint($type) unless eval { $type->isa('Data::TagDB::Tag') }; |
|
|
0
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
|
|
115
|
0
|
0
|
|
|
|
|
if ($autocreate) { |
|
116
|
0
|
|
|
|
|
|
return $self->create_tag([$type => $id]); |
|
117
|
|
|
|
|
|
|
} else { |
|
118
|
0
|
|
|
|
|
|
return $self->tag_by_dbid($self->_get_data(_tag_by_dbid_type_and_data => ($type->dbid, $id))); |
|
119
|
|
|
|
|
|
|
} |
|
120
|
|
|
|
|
|
|
} |
|
121
|
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
sub tag_by_specification { |
|
124
|
0
|
|
|
0
|
1
|
|
my ($self, $specification, %opts) = @_; |
|
125
|
0
|
|
|
|
|
|
my $wk = $self->wk; |
|
126
|
0
|
|
|
|
|
|
my $style = $opts{style}; |
|
127
|
0
|
|
|
|
|
|
my $important = $opts{important}; |
|
128
|
0
|
|
|
|
|
|
my $role = $opts{role}; |
|
129
|
0
|
|
|
|
|
|
my @candidates; |
|
130
|
|
|
|
|
|
|
|
|
131
|
0
|
0
|
0
|
|
|
|
croak 'No style given' unless defined($style) && length($style); |
|
132
|
|
|
|
|
|
|
|
|
133
|
0
|
0
|
|
|
|
|
if ($style eq 'ise') { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
134
|
0
|
|
0
|
|
|
|
@candidates = (eval { $self->tag_by_id(uuid => $specification) } // eval { $self->tag_by_id(oid => $specification) } // eval { $self->tag_by_id(uri => $specification) }); |
|
|
0
|
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
} elsif ($style eq 'tagpool') { |
|
136
|
0
|
0
|
|
|
|
|
unless ($opts{as_is}) { |
|
137
|
0
|
|
0
|
|
|
|
$important ||= $specification =~ s/\!$//; |
|
138
|
|
|
|
|
|
|
|
|
139
|
0
|
0
|
0
|
|
|
|
if (!defined($role) && $specification =~ s/^(.+)\@([^@]+)$/$2/) { |
|
140
|
0
|
|
|
|
|
|
$role = $self->tag_by_specification($1, %opts); |
|
141
|
|
|
|
|
|
|
} |
|
142
|
|
|
|
|
|
|
} |
|
143
|
|
|
|
|
|
|
|
|
144
|
0
|
0
|
|
|
|
|
if ($specification =~ /^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/) { |
|
145
|
0
|
|
|
|
|
|
return $self->tag_by_id(uuid => $specification); |
|
146
|
|
|
|
|
|
|
} |
|
147
|
|
|
|
|
|
|
|
|
148
|
0
|
|
|
|
|
|
@candidates = $self->metadata( |
|
149
|
|
|
|
|
|
|
relation => $wk->also_shares_identifier, |
|
150
|
|
|
|
|
|
|
type => $wk->tagname, |
|
151
|
|
|
|
|
|
|
encoding => undef, |
|
152
|
|
|
|
|
|
|
data_raw => $specification, |
|
153
|
|
|
|
|
|
|
)->collect('tag'); |
|
154
|
|
|
|
|
|
|
} elsif ($style eq 'sirtx') { |
|
155
|
0
|
|
|
|
|
|
my ($type, $id); |
|
156
|
0
|
|
|
|
|
|
my $backup_type; |
|
157
|
|
|
|
|
|
|
|
|
158
|
0
|
|
|
|
|
|
$specification =~ s/^\[(.+)\]$/$1/; |
|
159
|
|
|
|
|
|
|
|
|
160
|
0
|
0
|
|
|
|
|
if ($specification =~ /^\/([0-9]+)$/) { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
161
|
0
|
|
|
|
|
|
$id = $1; |
|
162
|
0
|
|
|
|
|
|
$type = $wk->sirtx_function_number; |
|
163
|
|
|
|
|
|
|
} elsif ($specification =~ /^\/([a-z_]+)$/) { |
|
164
|
0
|
|
|
|
|
|
$id = $1; |
|
165
|
0
|
|
|
|
|
|
$type = $wk->sirtx_function_name; |
|
166
|
|
|
|
|
|
|
} elsif ($specification =~ /^\*([0-9]+)$/) { |
|
167
|
0
|
|
|
|
|
|
@candidates = ($opts{sirtx_local_ids}{int $1}); |
|
168
|
|
|
|
|
|
|
} elsif ($specification eq '*') { |
|
169
|
0
|
|
|
|
|
|
@candidates = ($opts{sirtx_local_ids}{0}); |
|
170
|
|
|
|
|
|
|
} elsif ($specification =~ /^\'([0-9]+)$/) { |
|
171
|
0
|
|
|
|
|
|
my $num = int($1); |
|
172
|
0
|
|
|
|
|
|
require Data::Identifier::Generate; |
|
173
|
0
|
|
|
|
|
|
my $id = Data::Identifier::Generate->integer($num); |
|
174
|
0
|
|
|
|
|
|
@candidates = ($self->tag_by_id($id)); |
|
175
|
|
|
|
|
|
|
} elsif ($specification eq '\'') { |
|
176
|
0
|
|
|
|
|
|
@candidates = ($wk->zero); |
|
177
|
|
|
|
|
|
|
} elsif ($specification =~ /^[\&\%]([0-9a-zA-Z_]+)$/) { |
|
178
|
0
|
|
|
|
|
|
my $port_tag = $self->tag_by_specification($1, %opts); |
|
179
|
0
|
|
|
|
|
|
my $ports = $opts{sirtx_ports}; |
|
180
|
0
|
|
|
|
|
|
my $len = scalar(@{$ports}); |
|
|
0
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
|
|
182
|
0
|
|
0
|
|
|
|
for (my $i = 0; !scalar(@candidates) && $i < $len; $i += 2) { |
|
183
|
0
|
|
|
|
|
|
my $p = $ports->[$i]; |
|
184
|
0
|
0
|
0
|
|
|
|
if ($port_tag == $p || $port_tag->dbid eq $p->dbid) { |
|
185
|
0
|
|
|
|
|
|
@candidates = ($ports->[$i+1]); |
|
186
|
|
|
|
|
|
|
} |
|
187
|
|
|
|
|
|
|
} |
|
188
|
|
|
|
|
|
|
} elsif ($specification =~ /^(.+):(.+)$/) { |
|
189
|
0
|
|
|
|
|
|
($type, $id) = ($1, $2); |
|
190
|
|
|
|
|
|
|
} else { |
|
191
|
0
|
|
|
|
|
|
$type = $wk->sirtx_logical; |
|
192
|
0
|
|
|
|
|
|
$backup_type = $wk->sirtx_function_name; |
|
193
|
0
|
|
|
|
|
|
$id = $specification; |
|
194
|
|
|
|
|
|
|
} |
|
195
|
|
|
|
|
|
|
|
|
196
|
0
|
0
|
|
|
|
|
if (defined $type) { |
|
197
|
0
|
0
|
|
|
|
|
unless (ref $type) { |
|
198
|
0
|
|
|
|
|
|
$type = $self->tag_by_specification($type, %opts); |
|
199
|
|
|
|
|
|
|
} |
|
200
|
|
|
|
|
|
|
|
|
201
|
0
|
|
|
|
|
|
@candidates = $self->metadata( |
|
202
|
|
|
|
|
|
|
relation => $wk->also_shares_identifier, |
|
203
|
|
|
|
|
|
|
type => $type, |
|
204
|
|
|
|
|
|
|
encoding => undef, |
|
205
|
|
|
|
|
|
|
data_raw => $id, |
|
206
|
|
|
|
|
|
|
)->collect('tag'); |
|
207
|
|
|
|
|
|
|
|
|
208
|
0
|
0
|
0
|
|
|
|
if (scalar(@candidates) == 0 && defined($backup_type)) { |
|
209
|
0
|
|
|
|
|
|
@candidates = $self->metadata( |
|
210
|
|
|
|
|
|
|
relation => $wk->also_shares_identifier, |
|
211
|
|
|
|
|
|
|
type => $backup_type, |
|
212
|
|
|
|
|
|
|
encoding => undef, |
|
213
|
|
|
|
|
|
|
data_raw => $id, |
|
214
|
|
|
|
|
|
|
)->collect('tag'); |
|
215
|
|
|
|
|
|
|
} |
|
216
|
|
|
|
|
|
|
} |
|
217
|
|
|
|
|
|
|
} else { |
|
218
|
0
|
|
|
|
|
|
croak 'Invalid/unsupported style: '.$style; |
|
219
|
|
|
|
|
|
|
} |
|
220
|
|
|
|
|
|
|
|
|
221
|
0
|
0
|
|
|
|
|
if ($important) { |
|
222
|
0
|
|
|
|
|
|
@candidates = $self->relation( |
|
223
|
|
|
|
|
|
|
tag => \@candidates, |
|
224
|
|
|
|
|
|
|
relation => $wk->flagged_as, |
|
225
|
|
|
|
|
|
|
related => $wk->important, |
|
226
|
|
|
|
|
|
|
)->collect('tag'); |
|
227
|
|
|
|
|
|
|
} |
|
228
|
|
|
|
|
|
|
|
|
229
|
0
|
0
|
|
|
|
|
if (defined $role) { |
|
230
|
|
|
|
|
|
|
@candidates = grep { |
|
231
|
0
|
|
|
|
|
|
$_->cloudlet('roles')->is_entry($role) |
|
|
0
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
} @candidates; |
|
233
|
|
|
|
|
|
|
} |
|
234
|
|
|
|
|
|
|
|
|
235
|
0
|
0
|
|
|
|
|
if (scalar(@candidates) == 1) { |
|
|
|
0
|
|
|
|
|
|
|
236
|
0
|
|
|
|
|
|
return $candidates[0]; |
|
237
|
|
|
|
|
|
|
} elsif (scalar(@candidates) > 1) { |
|
238
|
0
|
|
|
|
|
|
croak 'Nore than one match found'; |
|
239
|
|
|
|
|
|
|
} else { |
|
240
|
0
|
|
|
|
|
|
croak 'Tag not found'; |
|
241
|
|
|
|
|
|
|
} |
|
242
|
|
|
|
|
|
|
|
|
243
|
0
|
|
|
|
|
|
die 'BUG'; |
|
244
|
|
|
|
|
|
|
} |
|
245
|
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
sub relation { |
|
248
|
0
|
|
|
0
|
1
|
|
my ($self, %opts) = @_; |
|
249
|
0
|
|
|
|
|
|
return $self->_link_iterator(%opts, package => 'Data::TagDB::Relation'); |
|
250
|
|
|
|
|
|
|
} |
|
251
|
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
sub metadata { |
|
254
|
0
|
|
|
0
|
1
|
|
my ($self, %opts) = @_; |
|
255
|
0
|
|
|
|
|
|
return $self->_link_iterator(%opts, package => 'Data::TagDB::Metadata'); |
|
256
|
|
|
|
|
|
|
} |
|
257
|
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
sub link { |
|
260
|
0
|
|
|
0
|
1
|
|
my ($self, %opts) = @_; |
|
261
|
0
|
|
|
|
|
|
return Data::TagDB::MultiIterator->new(db => $self, iterators => [ |
|
262
|
|
|
|
|
|
|
$self->metadata(%opts), |
|
263
|
|
|
|
|
|
|
$self->relation(%opts), |
|
264
|
|
|
|
|
|
|
]); |
|
265
|
|
|
|
|
|
|
} |
|
266
|
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
sub wk { |
|
269
|
0
|
|
|
0
|
1
|
|
my ($self) = @_; |
|
270
|
0
|
|
0
|
|
|
|
return $self->{wk} //= Data::TagDB::WellKnown->_new(db => $self); |
|
271
|
|
|
|
|
|
|
} |
|
272
|
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
sub register_decoder { |
|
275
|
0
|
|
|
0
|
1
|
|
my ($self, $type, $encoding, $decoder) = @_; |
|
276
|
0
|
|
0
|
|
|
|
my $decoders = $self->{decoders} //= $self->_register_basic_decoders; |
|
277
|
0
|
|
0
|
|
|
|
$decoders->{$type->dbid} //= {}; |
|
278
|
0
|
|
|
|
|
|
$decoders->{$type->dbid}{$encoding->dbid} = $decoder; |
|
279
|
|
|
|
|
|
|
} |
|
280
|
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
sub create_tag { |
|
283
|
0
|
|
|
0
|
1
|
|
my ($self, $ids, $addional_ids) = @_; |
|
284
|
0
|
|
|
|
|
|
my $asi = $self->wk->also_shares_identifier; |
|
285
|
0
|
|
|
|
|
|
my $asi_dbid = $asi->dbid; |
|
286
|
0
|
|
|
|
|
|
my $query_tpl = 'SELECT tag FROM metadata WHERE relation = '.$asi_dbid.' AND type = ? AND context = 0 AND encoding = 0 AND data = ?'; |
|
287
|
0
|
|
|
|
|
|
my $query = ''; |
|
288
|
0
|
|
|
|
|
|
my @bind; |
|
289
|
|
|
|
|
|
|
my $row; |
|
290
|
0
|
|
|
|
|
|
my $tag; |
|
291
|
|
|
|
|
|
|
|
|
292
|
0
|
0
|
0
|
|
|
|
if (blessed($ids) && $ids->isa('Data::Identifier')) { |
|
293
|
0
|
|
|
|
|
|
$ids = [$self->tag_by_id($ids->type) => $ids->id]; |
|
294
|
|
|
|
|
|
|
} |
|
295
|
|
|
|
|
|
|
|
|
296
|
0
|
0
|
0
|
|
|
|
if (blessed($addional_ids) && $addional_ids->isa('Data::Identifier')) { |
|
297
|
0
|
|
|
|
|
|
$addional_ids = [$self->tag_by_id($addional_ids->type) => $addional_ids->id]; |
|
298
|
|
|
|
|
|
|
} |
|
299
|
|
|
|
|
|
|
|
|
300
|
0
|
|
|
|
|
|
for (my $i = 0; $i < scalar(@{$ids}); $i += 2) { |
|
|
0
|
|
|
|
|
|
|
|
301
|
0
|
|
|
|
|
|
my $type = $ids->[$i + 0]; |
|
302
|
0
|
|
|
|
|
|
my $value = $ids->[$i + 1]; |
|
303
|
|
|
|
|
|
|
|
|
304
|
0
|
0
|
|
|
|
|
next unless defined $value; |
|
305
|
|
|
|
|
|
|
|
|
306
|
0
|
0
|
|
|
|
|
$query .= ' UNION ' if length $query; |
|
307
|
|
|
|
|
|
|
|
|
308
|
0
|
|
|
|
|
|
$query .= $query_tpl; |
|
309
|
0
|
|
|
|
|
|
push(@bind, $type->dbid, $value); |
|
310
|
|
|
|
|
|
|
} |
|
311
|
|
|
|
|
|
|
|
|
312
|
0
|
|
|
|
|
|
$query = $self->dbh->prepare($query); |
|
313
|
0
|
|
|
|
|
|
$query->execute(@bind); |
|
314
|
0
|
|
|
|
|
|
$row = $query->fetchrow_arrayref; |
|
315
|
0
|
|
|
|
|
|
$query->finish; |
|
316
|
|
|
|
|
|
|
|
|
317
|
0
|
0
|
0
|
|
|
|
if (defined($row) && defined($row->[0]) && $row->[0] > 0) { |
|
|
|
|
0
|
|
|
|
|
|
318
|
0
|
|
|
|
|
|
$tag = $self->tag_by_dbid($row->[0]); |
|
319
|
|
|
|
|
|
|
} else { |
|
320
|
0
|
|
|
|
|
|
$query = $self->_query('_create_tag'); |
|
321
|
0
|
|
|
|
|
|
$query->execute; |
|
322
|
0
|
0
|
|
|
|
|
if ($self->_DBI_name eq 'Pg') { |
|
323
|
0
|
|
|
|
|
|
my $row = $query->fetchrow_arrayref; |
|
324
|
0
|
|
|
|
|
|
$tag = $self->tag_by_dbid($row->[0]); |
|
325
|
|
|
|
|
|
|
} else { |
|
326
|
0
|
|
|
|
|
|
$tag = $self->tag_by_dbid($query->last_insert_id); |
|
327
|
|
|
|
|
|
|
} |
|
328
|
0
|
|
|
|
|
|
$query->finish; |
|
329
|
|
|
|
|
|
|
} |
|
330
|
|
|
|
|
|
|
|
|
331
|
0
|
|
|
|
|
|
for (my $i = 0; $i < scalar(@{$ids}); $i += 2) { |
|
|
0
|
|
|
|
|
|
|
|
332
|
0
|
|
|
|
|
|
my $type = $ids->[$i + 0]; |
|
333
|
0
|
|
|
|
|
|
my $value = $ids->[$i + 1]; |
|
334
|
|
|
|
|
|
|
|
|
335
|
0
|
|
|
|
|
|
$self->create_metadata(tag => $tag, relation => $asi, type => $type, data_raw => $value); |
|
336
|
|
|
|
|
|
|
} |
|
337
|
|
|
|
|
|
|
|
|
338
|
0
|
0
|
|
|
|
|
if (defined $addional_ids) { |
|
339
|
0
|
|
|
|
|
|
for (my $i = 0; $i < scalar(@{$addional_ids}); $i += 2) { |
|
|
0
|
|
|
|
|
|
|
|
340
|
0
|
|
|
|
|
|
my $type = $addional_ids->[$i + 0]; |
|
341
|
0
|
|
|
|
|
|
my $value = $addional_ids->[$i + 1]; |
|
342
|
|
|
|
|
|
|
|
|
343
|
0
|
0
|
|
|
|
|
next unless defined $value; |
|
344
|
|
|
|
|
|
|
|
|
345
|
0
|
|
|
|
|
|
$self->create_metadata(tag => $tag, relation => $asi, type => $type, data_raw => $value); |
|
346
|
|
|
|
|
|
|
} |
|
347
|
|
|
|
|
|
|
} |
|
348
|
|
|
|
|
|
|
|
|
349
|
0
|
|
|
|
|
|
return $tag; |
|
350
|
|
|
|
|
|
|
} |
|
351
|
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
sub create_metadata { |
|
354
|
0
|
|
|
0
|
1
|
|
my ($self, %opts) = @_; |
|
355
|
0
|
|
|
|
|
|
my $query = $self->_query('_create_metadata'); |
|
356
|
|
|
|
|
|
|
my @bind = ( |
|
357
|
|
|
|
|
|
|
$self->_as_tag($opts{tag}, 1)->dbid, |
|
358
|
|
|
|
|
|
|
$self->_as_tag($opts{relation}, 1)->dbid, |
|
359
|
|
|
|
|
|
|
Data::TagDB::Tag::dbid($self->_as_tag($opts{context}, 1)), |
|
360
|
|
|
|
|
|
|
Data::TagDB::Tag::dbid($self->_as_tag($opts{type}, 1)), |
|
361
|
|
|
|
|
|
|
Data::TagDB::Tag::dbid($self->_as_tag($opts{encoding}, 1)), |
|
362
|
|
|
|
|
|
|
$opts{data_raw}, |
|
363
|
0
|
|
|
|
|
|
); |
|
364
|
|
|
|
|
|
|
|
|
365
|
0
|
|
|
|
|
|
$query->execute(@bind); |
|
366
|
0
|
|
|
|
|
|
$query->finish; |
|
367
|
|
|
|
|
|
|
|
|
368
|
0
|
|
|
|
|
|
return Data::TagDB::Metadata->_new(%opts, db => $self); |
|
369
|
|
|
|
|
|
|
} |
|
370
|
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
sub create_relation { |
|
373
|
0
|
|
|
0
|
1
|
|
my ($self, %opts) = @_; |
|
374
|
0
|
|
|
|
|
|
my $query = $self->_query('_create_relation'); |
|
375
|
|
|
|
|
|
|
my @bind = ( |
|
376
|
|
|
|
|
|
|
$self->_as_tag($opts{tag}, 1)->dbid, |
|
377
|
|
|
|
|
|
|
$self->_as_tag($opts{relation}, 1)->dbid, |
|
378
|
|
|
|
|
|
|
$self->_as_tag($opts{related}, 1)->dbid, |
|
379
|
|
|
|
|
|
|
Data::TagDB::Tag::dbid($self->_as_tag($opts{context}), 1), |
|
380
|
0
|
|
|
|
|
|
Data::TagDB::Tag::dbid($self->_as_tag($opts{filter}), 1), |
|
381
|
|
|
|
|
|
|
); |
|
382
|
|
|
|
|
|
|
|
|
383
|
0
|
|
|
|
|
|
$query->execute(@bind); |
|
384
|
0
|
|
|
|
|
|
$query->finish; |
|
385
|
|
|
|
|
|
|
|
|
386
|
0
|
|
|
|
|
|
return Data::TagDB::Relation->_new(%opts, db => $self); |
|
387
|
|
|
|
|
|
|
} |
|
388
|
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
|
|
390
|
|
|
|
|
|
|
sub create_cache { |
|
391
|
0
|
|
|
0
|
1
|
|
my ($self) = @_; |
|
392
|
0
|
|
|
|
|
|
require Data::TagDB::Cache; |
|
393
|
0
|
|
|
|
|
|
return Data::TagDB::Cache->_new(db => $self); |
|
394
|
|
|
|
|
|
|
} |
|
395
|
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
sub migration { |
|
398
|
0
|
|
|
0
|
1
|
|
my ($self) = @_; |
|
399
|
0
|
|
|
|
|
|
require Data::TagDB::Migration; |
|
400
|
0
|
|
0
|
|
|
|
return $self->{migration} //= Data::TagDB::Migration->_new(db => $self); |
|
401
|
|
|
|
|
|
|
} |
|
402
|
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
sub factory { |
|
405
|
0
|
|
|
0
|
1
|
|
my ($self) = @_; |
|
406
|
0
|
|
|
|
|
|
require Data::TagDB::Factory; |
|
407
|
0
|
|
0
|
|
|
|
return $self->{factory} //= Data::TagDB::Factory->_new(db => $self); |
|
408
|
|
|
|
|
|
|
} |
|
409
|
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
sub exporter { |
|
412
|
0
|
|
|
0
|
1
|
|
my ($self, $target, %opts) = @_; |
|
413
|
0
|
|
|
|
|
|
require Data::TagDB::Exporter; |
|
414
|
0
|
|
|
|
|
|
return Data::TagDB::Exporter->_new(db => $self, target => $target, %opts); |
|
415
|
|
|
|
|
|
|
} |
|
416
|
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
sub begin_work { |
|
419
|
0
|
|
|
0
|
1
|
|
my ($self, @args) = @_; |
|
420
|
0
|
0
|
0
|
|
|
|
croak 'Transaction already in process' if $self->{transaction_refc} || defined($self->{transaction_type}); |
|
421
|
0
|
|
|
|
|
|
$self->{transaction_refc} = 1; |
|
422
|
0
|
|
|
|
|
|
return $self->dbh->begin_work(@args); |
|
423
|
|
|
|
|
|
|
} |
|
424
|
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
sub commit { |
|
426
|
0
|
|
|
0
|
1
|
|
my ($self, @args) = @_; |
|
427
|
0
|
0
|
|
|
|
|
croak 'No transaction in process' unless $self->{transaction_refc}; |
|
428
|
0
|
|
|
|
|
|
$self->{transaction_refc}--; |
|
429
|
0
|
0
|
|
|
|
|
return if $self->{transaction_refc}; |
|
430
|
0
|
|
|
|
|
|
return $self->dbh->commit(@args); |
|
431
|
|
|
|
|
|
|
} |
|
432
|
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
sub rollback { |
|
434
|
0
|
|
|
0
|
1
|
|
my ($self, @args) = @_; |
|
435
|
0
|
0
|
|
|
|
|
croak 'No transaction in process' unless $self->{transaction_refc}; |
|
436
|
0
|
|
|
|
|
|
$self->{transaction_refc}--; |
|
437
|
0
|
0
|
|
|
|
|
return if $self->{transaction_refc}; |
|
438
|
0
|
|
|
|
|
|
return $self->dbh->rollback(@args); |
|
439
|
|
|
|
|
|
|
} |
|
440
|
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
sub in_transaction { |
|
443
|
0
|
|
|
0
|
1
|
|
my ($self, $type, $code) = @_; |
|
444
|
0
|
|
|
|
|
|
my $error; |
|
445
|
|
|
|
|
|
|
|
|
446
|
0
|
0
|
0
|
|
|
|
croak 'Bad transaction type' unless $type eq 'ro' || $type eq 'rw'; |
|
447
|
0
|
0
|
|
|
|
|
croak 'Transaction already in process' if $self->{transaction_refc}; |
|
448
|
|
|
|
|
|
|
|
|
449
|
0
|
0
|
|
|
|
|
unless (defined($self->{transaction_type})) { |
|
450
|
0
|
|
|
|
|
|
$self->{transaction_type} = $type; |
|
451
|
0
|
|
|
|
|
|
$self->{transaction_open} = 0; |
|
452
|
0
|
|
|
|
|
|
$self->dbh->begin_work; |
|
453
|
|
|
|
|
|
|
} |
|
454
|
|
|
|
|
|
|
|
|
455
|
0
|
0
|
0
|
|
|
|
if ($self->{transaction_type} eq $type || $self->{transaction_type} eq 'rw') { |
|
|
|
0
|
0
|
|
|
|
|
|
456
|
|
|
|
|
|
|
# no-op |
|
457
|
|
|
|
|
|
|
} elsif ($self->{transaction_type} eq 'ro' && $type eq 'rw') { |
|
458
|
0
|
|
|
|
|
|
$self->{transaction_type} = $type; |
|
459
|
|
|
|
|
|
|
} else { |
|
460
|
0
|
|
|
|
|
|
$error = 'Transaction type missmatch'; |
|
461
|
|
|
|
|
|
|
} |
|
462
|
|
|
|
|
|
|
|
|
463
|
0
|
0
|
|
|
|
|
unless (defined $error) { |
|
464
|
0
|
|
|
|
|
|
$self->{transaction_open}++; |
|
465
|
0
|
|
|
|
|
|
eval { $code->() }; |
|
|
0
|
|
|
|
|
|
|
|
466
|
0
|
|
|
|
|
|
$self->{transaction_open}--; |
|
467
|
|
|
|
|
|
|
} |
|
468
|
|
|
|
|
|
|
|
|
469
|
0
|
0
|
|
|
|
|
unless ($self->{transaction_open}) { |
|
470
|
0
|
|
|
|
|
|
delete $self->{transaction_type}; |
|
471
|
0
|
|
|
|
|
|
$self->dbh->commit; |
|
472
|
|
|
|
|
|
|
} |
|
473
|
|
|
|
|
|
|
|
|
474
|
0
|
0
|
|
|
|
|
croak $error if defined $error; |
|
475
|
|
|
|
|
|
|
} |
|
476
|
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
# ---- Virtual methods ---- |
|
478
|
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
# ---- Private helpers ---- |
|
480
|
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
sub DESTROY { |
|
482
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
|
483
|
0
|
|
|
|
|
|
eval { $self->disconnect }; |
|
|
0
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
} |
|
485
|
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
sub assert_connected { |
|
487
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
|
488
|
0
|
|
|
|
|
|
my $dbh = $self->{dbh}; |
|
489
|
0
|
0
|
|
|
|
|
confess 'Not connected to any database' unless defined $dbh; |
|
490
|
0
|
|
|
|
|
|
return $dbh; |
|
491
|
|
|
|
|
|
|
} |
|
492
|
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
sub tag_by_dbid { |
|
494
|
0
|
|
|
0
|
0
|
|
my ($self, $dbid) = @_; |
|
495
|
0
|
|
|
|
|
|
my $cache = $self->{cache_tag}; |
|
496
|
0
|
0
|
|
|
|
|
if (defined $cache->{$dbid}) { |
|
497
|
0
|
|
|
|
|
|
return $cache->{$dbid}; |
|
498
|
|
|
|
|
|
|
} else { |
|
499
|
0
|
|
|
|
|
|
state $done = 0; |
|
500
|
0
|
|
|
|
|
|
my $tag = Data::TagDB::Tag->_new(db => $self, dbid => $dbid); |
|
501
|
|
|
|
|
|
|
|
|
502
|
0
|
0
|
|
|
|
|
if ($done++ > 1024) { |
|
503
|
0
|
|
|
|
|
|
$self->_cache_maintain; |
|
504
|
0
|
|
|
|
|
|
$done = 0; |
|
505
|
|
|
|
|
|
|
} |
|
506
|
|
|
|
|
|
|
|
|
507
|
0
|
|
|
|
|
|
$cache->{$dbid} = $tag; |
|
508
|
0
|
|
|
|
|
|
weaken($cache->{$dbid}); |
|
509
|
|
|
|
|
|
|
|
|
510
|
0
|
|
|
|
|
|
return $tag; |
|
511
|
|
|
|
|
|
|
} |
|
512
|
|
|
|
|
|
|
} |
|
513
|
|
|
|
|
|
|
|
|
514
|
|
|
|
|
|
|
sub _tag_by_ise_cached { |
|
515
|
0
|
|
|
0
|
|
|
my ($self, $ise, $autocreate) = @_; |
|
516
|
0
|
0
|
|
|
|
|
if (defined $self->{cache_ise}{$ise}) { |
|
517
|
0
|
|
|
|
|
|
return $self->tag_by_dbid($self->{cache_ise}{$ise}); |
|
518
|
|
|
|
|
|
|
} else { |
|
519
|
0
|
|
|
|
|
|
my $tag = $self->tag_by_id(uuid => $ise, $autocreate); # TODO: Allow all ISE here. |
|
520
|
0
|
|
|
|
|
|
$self->{cache_ise}{$ise} = $tag->dbid; |
|
521
|
0
|
|
|
|
|
|
return $tag; |
|
522
|
|
|
|
|
|
|
} |
|
523
|
|
|
|
|
|
|
} |
|
524
|
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
sub _cache_maintain { |
|
526
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
|
527
|
0
|
|
|
|
|
|
my $cache = $self->{cache_tag}; |
|
528
|
|
|
|
|
|
|
|
|
529
|
0
|
|
|
|
|
|
foreach my $key (keys %{$cache}) { |
|
|
0
|
|
|
|
|
|
|
|
530
|
0
|
0
|
|
|
|
|
delete $cache->{$key} unless defined $cache->{$key}; |
|
531
|
|
|
|
|
|
|
} |
|
532
|
|
|
|
|
|
|
} |
|
533
|
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
sub _cache_clear { |
|
535
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
|
536
|
0
|
|
|
|
|
|
$self->_cache_maintain; |
|
537
|
0
|
|
|
|
|
|
%{$self->{cache_ise}} = (); |
|
|
0
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
} |
|
539
|
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
sub _as_tag { |
|
541
|
0
|
|
|
0
|
|
|
my ($self, $id, $autocreate) = @_; |
|
542
|
0
|
0
|
|
|
|
|
return undef unless defined $id; |
|
543
|
0
|
0
|
|
|
|
|
return $id if eval {$id->isa('Data::TagDB::Tag')}; |
|
|
0
|
|
|
|
|
|
|
|
544
|
0
|
|
|
|
|
|
return $self->tag_by_id(Data::Identifier->new(from => $id, db => $self), $autocreate); |
|
545
|
|
|
|
|
|
|
} |
|
546
|
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
sub _default_type { |
|
548
|
0
|
|
|
0
|
|
|
my ($self, $relation) = @_; |
|
549
|
0
|
|
|
|
|
|
my $relation_dbid = $relation->dbid; |
|
550
|
0
|
0
|
|
|
|
|
if (defined $self->{cache_default_type}{$relation_dbid}) { |
|
551
|
0
|
|
|
|
|
|
return $self->tag_by_dbid($self->{cache_default_type}{$relation_dbid}); |
|
552
|
|
|
|
|
|
|
} else { |
|
553
|
0
|
|
|
|
|
|
my $type = eval {$self->relation(tag => $relation, relation => $self->wk->default_type)->one->related}; |
|
|
0
|
|
|
|
|
|
|
|
554
|
0
|
0
|
|
|
|
|
if (defined $type) { |
|
|
|
0
|
|
|
|
|
|
|
555
|
0
|
|
|
|
|
|
$self->{cache_default_type}{$relation_dbid} = $type->dbid; |
|
556
|
|
|
|
|
|
|
} elsif (defined $self->{backup_type}{$relation_dbid}) { |
|
557
|
0
|
|
|
|
|
|
return $self->tag_by_dbid($self->{cache_default_type}{$relation_dbid} = $self->{backup_type}{$relation_dbid}); |
|
558
|
|
|
|
|
|
|
} else { |
|
559
|
0
|
|
|
|
|
|
die 'No default type known'; |
|
560
|
|
|
|
|
|
|
} |
|
561
|
0
|
|
|
|
|
|
return $type; |
|
562
|
|
|
|
|
|
|
} |
|
563
|
|
|
|
|
|
|
} |
|
564
|
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
sub _default_encoding { |
|
566
|
0
|
|
|
0
|
|
|
my ($self, $type) = @_; |
|
567
|
0
|
|
|
|
|
|
my $type_dbid = $type->dbid; |
|
568
|
0
|
0
|
|
|
|
|
if (defined $self->{cache_default_encoding}{$type_dbid}) { |
|
569
|
0
|
|
|
|
|
|
return $self->tag_by_dbid($self->{cache_default_encoding}{$type_dbid}); |
|
570
|
|
|
|
|
|
|
} else { |
|
571
|
0
|
|
|
|
|
|
my $encoding = $self->relation(tag => $type, relation => $self->wk->default_encoding)->one->related; |
|
572
|
0
|
|
|
|
|
|
$self->{cache_default_encoding}{$type_dbid} = $encoding->dbid; |
|
573
|
0
|
|
|
|
|
|
return $encoding; |
|
574
|
|
|
|
|
|
|
} |
|
575
|
|
|
|
|
|
|
} |
|
576
|
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
sub _register_backup_type { |
|
578
|
0
|
|
|
0
|
|
|
my ($self, $relation, $type) = @_; |
|
579
|
0
|
|
|
|
|
|
$self->{backup_type}{$relation->dbid} = $type->dbid; |
|
580
|
|
|
|
|
|
|
} |
|
581
|
|
|
|
|
|
|
|
|
582
|
|
|
|
|
|
|
sub _register_basic_decoders { |
|
583
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
|
584
|
0
|
|
0
|
|
|
|
my $decoders = $self->{decoders} //= {}; |
|
585
|
0
|
|
|
|
|
|
my $wk = $self->wk; |
|
586
|
0
|
|
|
0
|
|
|
my $decode_string = sub { $_[0]->data_raw }; |
|
|
0
|
|
|
|
|
|
|
|
587
|
0
|
|
|
0
|
|
|
my $decode_uri = sub { URI->new($_[0]->data_raw) }; |
|
|
0
|
|
|
|
|
|
|
|
588
|
0
|
0
|
|
0
|
|
|
my $decode_int = sub { my $v = $_[0]->data_raw; croak 'Bad data' unless $v =~ /^[0-9]+$/; int($v) }; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
589
|
0
|
|
|
0
|
|
|
my $decode_colour = sub { Data::URIID::Colour->new(rgb => $_[0]->data_raw) }; |
|
|
0
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
|
|
591
|
0
|
|
|
|
|
|
eval { $self->register_decoder($wk->uuid, $wk->string_ise_uuid_encoding, $decode_string) }; |
|
|
0
|
|
|
|
|
|
|
|
592
|
0
|
|
|
|
|
|
eval { $self->register_decoder($wk->oid, $wk->string_ise_oid_encoding, $decode_string) }; |
|
|
0
|
|
|
|
|
|
|
|
593
|
0
|
|
|
|
|
|
eval { $self->register_decoder($wk->uri, $wk->ascii_uri_encoding, $decode_uri) }; |
|
|
0
|
|
|
|
|
|
|
|
594
|
0
|
|
|
|
|
|
eval { $self->register_decoder($wk->tagname, $wk->utf_8_string_encoding, $decode_string) }; |
|
|
0
|
|
|
|
|
|
|
|
595
|
0
|
|
|
|
|
|
eval { $self->register_decoder($wk->x11_colour_name, $wk->utf_8_string_encoding, $decode_string) }; |
|
|
0
|
|
|
|
|
|
|
|
596
|
0
|
|
|
|
|
|
eval { $self->register_decoder($wk->wikidata_identifier, $wk->utf_8_string_encoding, $decode_string) }; |
|
|
0
|
|
|
|
|
|
|
|
597
|
0
|
|
|
|
|
|
eval { $self->register_decoder($wk->small_identifier, $wk->ascii_decimal_integer_encoding, $decode_int) }; |
|
|
0
|
|
|
|
|
|
|
|
598
|
0
|
|
|
|
|
|
eval { $self->register_decoder($wk->unsigned_integer, $wk->ascii_decimal_integer_encoding, $decode_int) }; |
|
|
0
|
|
|
|
|
|
|
|
599
|
0
|
|
|
|
|
|
eval { $self->register_decoder($wk->unicode_string, $wk->utf_8_string_encoding, $decode_string) }; |
|
|
0
|
|
|
|
|
|
|
|
600
|
0
|
|
|
|
|
|
eval { $self->register_decoder($wk->colour_value, $wk->hex_rgb_encoding, $decode_colour) }; |
|
|
0
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
|
|
602
|
0
|
|
|
|
|
|
eval { $self->_register_backup_type($wk->wd_unicode_character, $wk->unicode_string) }; |
|
|
0
|
|
|
|
|
|
|
|
603
|
0
|
|
|
|
|
|
eval { $self->_register_backup_type($wk->tagpool_tag_icontext, $wk->unicode_string) }; |
|
|
0
|
|
|
|
|
|
|
|
604
|
0
|
|
|
|
|
|
eval { $self->_register_backup_type($wk->also_has_description, $wk->unicode_string) }; |
|
|
0
|
|
|
|
|
|
|
|
605
|
0
|
|
|
|
|
|
eval { $self->_register_backup_type($wk->final_file_size, $wk->unsigned_integer) }; |
|
|
0
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
|
|
607
|
0
|
|
|
|
|
|
return $decoders; |
|
608
|
|
|
|
|
|
|
} |
|
609
|
|
|
|
|
|
|
|
|
610
|
|
|
|
|
|
|
sub _get_decoder { |
|
611
|
0
|
|
|
0
|
|
|
my ($self, $metadata) = @_; |
|
612
|
0
|
|
0
|
|
|
|
my $decoders = $self->{decoders} //= $self->_register_basic_decoders; |
|
613
|
0
|
|
0
|
|
|
|
my $for_type = $decoders->{$metadata->type_evaluated->dbid} //= {}; |
|
614
|
0
|
|
0
|
|
|
|
return $for_type->{$metadata->encoding_evaluated->dbid} // croak 'No matching decoder found'; |
|
615
|
|
|
|
|
|
|
} |
|
616
|
|
|
|
|
|
|
|
|
617
|
|
|
|
|
|
|
sub _DBI_name { |
|
618
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
|
619
|
0
|
|
0
|
|
|
|
return $self->{_DBI_name} //= $self->dbh->{Driver}{Name}; |
|
620
|
|
|
|
|
|
|
} |
|
621
|
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
sub _query { |
|
623
|
0
|
|
|
0
|
|
|
my ($self, $name) = @_; |
|
624
|
0
|
|
|
|
|
|
$self->assert_connected; |
|
625
|
0
|
|
0
|
|
|
|
return $self->{query}{$name} // confess 'No such query: '.$name; |
|
626
|
|
|
|
|
|
|
} |
|
627
|
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
sub _get_data { |
|
629
|
0
|
|
|
0
|
|
|
my ($self, $name, @args) = @_; |
|
630
|
0
|
|
|
|
|
|
my $query = $self->_query($name); |
|
631
|
0
|
|
|
|
|
|
my $row; |
|
632
|
|
|
|
|
|
|
|
|
633
|
0
|
|
|
|
|
|
$query->execute(@args); |
|
634
|
0
|
|
|
|
|
|
$row = $query->fetchrow_arrayref; |
|
635
|
0
|
|
|
|
|
|
$query->finish; |
|
636
|
|
|
|
|
|
|
|
|
637
|
0
|
0
|
|
|
|
|
croak 'No such entry' unless defined $row; |
|
638
|
|
|
|
|
|
|
|
|
639
|
0
|
|
|
|
|
|
return $row->[0]; |
|
640
|
|
|
|
|
|
|
} |
|
641
|
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
sub _build_query { |
|
643
|
0
|
|
|
0
|
|
|
my ($self, %opts) = @_; |
|
644
|
0
|
|
|
|
|
|
my %parts; |
|
645
|
|
|
|
|
|
|
my @where; |
|
646
|
0
|
|
|
|
|
|
my @binds; |
|
647
|
|
|
|
|
|
|
|
|
648
|
0
|
0
|
|
|
|
|
if ($opts{package} eq 'Data::TagDB::Metadata') { |
|
649
|
0
|
|
|
|
|
|
$parts{FROM} = 'metadata'; |
|
650
|
0
|
|
|
|
|
|
$parts{SELECT} = '*'; # TODO |
|
651
|
|
|
|
|
|
|
} else { |
|
652
|
0
|
|
|
|
|
|
$parts{FROM} = 'relation'; |
|
653
|
0
|
|
|
|
|
|
$parts{SELECT} = '*'; # TODO |
|
654
|
|
|
|
|
|
|
} |
|
655
|
|
|
|
|
|
|
|
|
656
|
0
|
0
|
|
|
|
|
if (defined $opts{limit}) { |
|
657
|
0
|
|
|
|
|
|
$parts{LIMIT} = $opts{limit}; |
|
658
|
|
|
|
|
|
|
} |
|
659
|
|
|
|
|
|
|
|
|
660
|
0
|
|
|
|
|
|
foreach my $key (qw(tag relation context filter related type encoding)) { |
|
661
|
0
|
|
|
|
|
|
foreach my $neg (0, 1) { |
|
662
|
0
|
0
|
|
|
|
|
my $curkey = ($neg ? 'no_' : '').$key; |
|
663
|
0
|
0
|
|
|
|
|
if (defined $opts{$curkey}) { |
|
664
|
0
|
0
|
|
|
|
|
my @list = ref($opts{$curkey}) eq 'ARRAY' ? @{$opts{$curkey}} : ($opts{$curkey}); |
|
|
0
|
|
|
|
|
|
|
|
665
|
|
|
|
|
|
|
|
|
666
|
0
|
|
|
|
|
|
foreach my $ent (@list) { |
|
667
|
0
|
0
|
|
|
|
|
croak 'Something not a Data::TagDB::Tag used as Tag filter' unless $ent->isa('Data::TagDB::Tag'); |
|
668
|
|
|
|
|
|
|
} |
|
669
|
|
|
|
|
|
|
|
|
670
|
0
|
0
|
|
|
|
|
push(@where, sprintf('%s %sIN (%s)', $key, $neg ? 'NOT ' : '', join(',', map {$_->dbid} @list))); |
|
|
0
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
} |
|
672
|
|
|
|
|
|
|
} |
|
673
|
|
|
|
|
|
|
} |
|
674
|
|
|
|
|
|
|
|
|
675
|
0
|
0
|
|
|
|
|
if (defined $opts{data_raw}) { |
|
676
|
0
|
|
|
|
|
|
push(@where, 'data = ?'); |
|
677
|
0
|
|
|
|
|
|
push(@binds, $opts{data_raw}); |
|
678
|
|
|
|
|
|
|
} |
|
679
|
|
|
|
|
|
|
|
|
680
|
0
|
0
|
|
|
|
|
if (scalar(@where)) { |
|
681
|
0
|
|
|
|
|
|
$parts{WHERE} = join(' AND ', @where); |
|
682
|
|
|
|
|
|
|
} |
|
683
|
|
|
|
|
|
|
|
|
684
|
0
|
0
|
|
|
|
|
if (defined $opts{order_by}) { |
|
685
|
0
|
0
|
|
|
|
|
my @list = ref($opts{order_by}) eq 'ARRAY' ? @{$opts{order_by}} : ($opts{order_by}); |
|
|
0
|
|
|
|
|
|
|
|
686
|
0
|
0
|
|
|
|
|
if (scalar @list) { |
|
687
|
|
|
|
|
|
|
$parts{ORDER} = 'BY '.join(', ', |
|
688
|
0
|
|
|
|
|
|
map {sprintf('%s ASC', $_)} @list |
|
|
0
|
|
|
|
|
|
|
|
689
|
|
|
|
|
|
|
); |
|
690
|
|
|
|
|
|
|
} |
|
691
|
|
|
|
|
|
|
} |
|
692
|
|
|
|
|
|
|
|
|
693
|
|
|
|
|
|
|
{ |
|
694
|
0
|
|
|
|
|
|
my $q = ''; |
|
|
0
|
|
|
|
|
|
|
|
695
|
0
|
|
|
|
|
|
my $sth; |
|
696
|
|
|
|
|
|
|
|
|
697
|
0
|
|
|
|
|
|
foreach my $key (qw(SELECT FROM WHERE ORDER LIMIT)) { |
|
698
|
0
|
0
|
|
|
|
|
if (defined $parts{$key}) { |
|
699
|
0
|
0
|
|
|
|
|
$q .= ' ' if length $q; |
|
700
|
0
|
|
|
|
|
|
$q .= $key.' '.$parts{$key}; |
|
701
|
|
|
|
|
|
|
} |
|
702
|
|
|
|
|
|
|
} |
|
703
|
|
|
|
|
|
|
|
|
704
|
0
|
|
|
|
|
|
$sth = $self->dbh->prepare($q); |
|
705
|
0
|
|
|
|
|
|
$sth->execute(@binds); |
|
706
|
0
|
|
|
|
|
|
return $sth; |
|
707
|
|
|
|
|
|
|
} |
|
708
|
|
|
|
|
|
|
} |
|
709
|
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
sub _link_iterator { |
|
711
|
0
|
|
|
0
|
|
|
my ($self, %opts) = @_; |
|
712
|
0
|
|
|
|
|
|
my $query = $self->_build_query(%opts); |
|
713
|
0
|
|
|
|
|
|
my %args; |
|
714
|
|
|
|
|
|
|
|
|
715
|
0
|
0
|
|
|
|
|
if ($opts{package} eq 'Data::TagDB::Metadata') { |
|
716
|
0
|
|
|
|
|
|
$args{tag_keys} = {map{$_ => $_} qw(type encoding)}; |
|
|
0
|
|
|
|
|
|
|
|
717
|
0
|
|
|
|
|
|
$args{raw_keys} = {data_raw => 'data'}; |
|
718
|
|
|
|
|
|
|
} else { |
|
719
|
0
|
|
|
|
|
|
$args{tag_keys} = {map{$_ => $_} qw(filter related)}; |
|
|
0
|
|
|
|
|
|
|
|
720
|
0
|
|
|
|
|
|
$args{raw_keys} = {}; # empty |
|
721
|
|
|
|
|
|
|
} |
|
722
|
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
# Add common keys: |
|
724
|
0
|
|
|
|
|
|
$args{tag_keys}{$_} = $_ foreach qw(tag relation context); |
|
725
|
|
|
|
|
|
|
|
|
726
|
0
|
|
|
|
|
|
return Data::TagDB::LinkIterator->new(%args, db => $self, query => $query, package => $opts{package}); |
|
727
|
|
|
|
|
|
|
} |
|
728
|
|
|
|
|
|
|
|
|
729
|
|
|
|
|
|
|
sub _load_cloudlet { |
|
730
|
0
|
|
|
0
|
|
|
my ($self, %opts) = @_; |
|
731
|
0
|
|
|
|
|
|
my $direct = $opts{direct}; |
|
732
|
0
|
|
|
|
|
|
my $indirect = $opts{indirect}; |
|
733
|
|
|
|
|
|
|
|
|
734
|
0
|
0
|
|
|
|
|
$direct = [$direct] unless ref($direct) eq 'ARRAY'; |
|
735
|
0
|
0
|
0
|
|
|
|
$indirect = [$indirect] unless ref($indirect) eq 'ARRAY' || !defined($indirect); |
|
736
|
|
|
|
|
|
|
|
|
737
|
0
|
0
|
|
|
|
|
return Data::TagDB::Cloudlet->new(db => $self, root => []) unless scalar(@{$direct}); |
|
|
0
|
|
|
|
|
|
|
|
738
|
|
|
|
|
|
|
|
|
739
|
0
|
0
|
0
|
|
|
|
if (defined($indirect) && !scalar(@{$indirect})) { |
|
|
0
|
|
|
|
|
|
|
|
740
|
0
|
|
|
|
|
|
$indirect = undef; |
|
741
|
|
|
|
|
|
|
} |
|
742
|
|
|
|
|
|
|
|
|
743
|
0
|
0
|
|
|
|
|
if (defined $opts{indirect}) { |
|
744
|
0
|
|
|
|
|
|
my $query = 'WITH RECURSIVE X(related,root) AS (SELECT related,true FROM relation WHERE tag = ? AND relation IN ('.join(',', map{'?'} @{$direct}).') UNION SELECT relation.related,false FROM relation, X WHERE relation.relation IN ('.join(',', map{'?'} @{$indirect}).') AND relation.tag = X.related) SELECT related,root FROM X'; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
745
|
0
|
|
|
|
|
|
my @bind = ($opts{tag}->dbid, map {$_->dbid} @{$direct}, @{$indirect}); |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
746
|
0
|
|
|
|
|
|
my $sth = $self->dbh->prepare($query); |
|
747
|
0
|
|
|
|
|
|
my @root; |
|
748
|
|
|
|
|
|
|
my @entry; |
|
749
|
|
|
|
|
|
|
|
|
750
|
0
|
|
|
|
|
|
$sth->execute(@bind); |
|
751
|
0
|
|
|
|
|
|
while (my $row = $sth->fetchrow_arrayref) { |
|
752
|
0
|
|
|
|
|
|
my $ent = $self->tag_by_dbid($row->[0]); |
|
753
|
0
|
0
|
|
|
|
|
if ($row->[1]) { |
|
754
|
0
|
|
|
|
|
|
push(@root, $ent); |
|
755
|
|
|
|
|
|
|
} else { |
|
756
|
0
|
|
|
|
|
|
push(@entry, $ent); |
|
757
|
|
|
|
|
|
|
} |
|
758
|
|
|
|
|
|
|
} |
|
759
|
0
|
|
|
|
|
|
$sth->finish; |
|
760
|
0
|
|
|
|
|
|
return Data::TagDB::Cloudlet->new(db => $self, root => \@root, entry => \@entry); |
|
761
|
|
|
|
|
|
|
} else { |
|
762
|
|
|
|
|
|
|
return Data::TagDB::Cloudlet->new(db => $self, root => [ |
|
763
|
0
|
|
|
|
|
|
$self->relation(tag => $opts{tag}, relation => $opts{direct})->collect('related') |
|
764
|
|
|
|
|
|
|
]); |
|
765
|
|
|
|
|
|
|
} |
|
766
|
|
|
|
|
|
|
|
|
767
|
|
|
|
|
|
|
# WITH RECURSIVE X(related,root) AS (SELECT related,true FROM relation WHERE tag = 597 AND relation IN (7, 201) UNION SELECT relation.related,false FROM relation, X WHERE relation.relation = 140 AND relation.tag = X.related) SELECT *,(SELECT data FROM metadata WHERE tag = X.related AND relation = 1 AND type = 5 LIMIT 1) FROM X |
|
768
|
|
|
|
|
|
|
} |
|
769
|
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
# ---- AUTOLOAD ---- |
|
771
|
|
|
|
|
|
|
|
|
772
|
|
|
|
|
|
|
sub AUTOLOAD { |
|
773
|
0
|
|
|
0
|
|
|
my ($self, @args) = @_; |
|
774
|
0
|
|
|
|
|
|
our $AUTOLOAD; |
|
775
|
0
|
|
|
|
|
|
my $function = $AUTOLOAD =~ s/^.*:://r; |
|
776
|
0
|
|
0
|
|
|
|
my $query = $self->{query}{$function} // confess 'Bad function: '.$function; |
|
777
|
|
|
|
|
|
|
|
|
778
|
0
|
0
|
|
|
|
|
if ($function =~ /^tag_by_/) { |
|
779
|
0
|
|
|
|
|
|
my $row; |
|
780
|
|
|
|
|
|
|
|
|
781
|
0
|
|
|
|
|
|
$query->execute(@args); |
|
782
|
0
|
|
|
|
|
|
$row = $query->fetchrow_hashref; |
|
783
|
0
|
|
|
|
|
|
$query->finish; |
|
784
|
|
|
|
|
|
|
|
|
785
|
0
|
0
|
0
|
|
|
|
croak 'No such tag' unless defined($row->{tag}) && $row->{tag} > 0; |
|
786
|
|
|
|
|
|
|
|
|
787
|
0
|
|
|
|
|
|
return $self->tag_by_dbid($row->{tag}); |
|
788
|
|
|
|
|
|
|
} else { |
|
789
|
0
|
|
|
|
|
|
confess 'Unsupported function with know query: '.$function; |
|
790
|
|
|
|
|
|
|
} |
|
791
|
|
|
|
|
|
|
} |
|
792
|
|
|
|
|
|
|
|
|
793
|
|
|
|
|
|
|
1; |
|
794
|
|
|
|
|
|
|
|
|
795
|
|
|
|
|
|
|
__END__ |
|
796
|
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
=pod |
|
798
|
|
|
|
|
|
|
|
|
799
|
|
|
|
|
|
|
=encoding UTF-8 |
|
800
|
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
=head1 NAME |
|
802
|
|
|
|
|
|
|
|
|
803
|
|
|
|
|
|
|
Data::TagDB - Work with Tag databases |
|
804
|
|
|
|
|
|
|
|
|
805
|
|
|
|
|
|
|
=head1 VERSION |
|
806
|
|
|
|
|
|
|
|
|
807
|
|
|
|
|
|
|
version v0.12 |
|
808
|
|
|
|
|
|
|
|
|
809
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
810
|
|
|
|
|
|
|
|
|
811
|
|
|
|
|
|
|
use Data::TagDB; |
|
812
|
|
|
|
|
|
|
|
|
813
|
|
|
|
|
|
|
my $db = Data::TagDB->new($dsn, ...); |
|
814
|
|
|
|
|
|
|
# or: |
|
815
|
|
|
|
|
|
|
my $db = Data::TagDB->new($dbh); |
|
816
|
|
|
|
|
|
|
|
|
817
|
|
|
|
|
|
|
# Create new database: |
|
818
|
|
|
|
|
|
|
use Data::TagDB::Migration; |
|
819
|
|
|
|
|
|
|
my Data::TagDB $db = Data::TagDB::Migration->create(...); |
|
820
|
|
|
|
|
|
|
|
|
821
|
|
|
|
|
|
|
This module implements SQL based universal tag databases. Such databases can be used to store any kind of (semantic) data. |
|
822
|
|
|
|
|
|
|
|
|
823
|
|
|
|
|
|
|
This module and it's submodule implement creation of databases, migration (to most current scheme), |
|
824
|
|
|
|
|
|
|
adding data and reading data from the database. |
|
825
|
|
|
|
|
|
|
|
|
826
|
|
|
|
|
|
|
For an introduction see L<Data::TagDB::Tutorial>. |
|
827
|
|
|
|
|
|
|
|
|
828
|
|
|
|
|
|
|
The instances of L<Data::TagDB::Tag> repesent any kind of object (may it be file, user account or a real life object like a tree). |
|
829
|
|
|
|
|
|
|
It provides some convenience functions such as to query objects for their name. |
|
830
|
|
|
|
|
|
|
|
|
831
|
|
|
|
|
|
|
L<Data::TagDB::Factory> (via L</factory>) is provided for easy creation of new tags. |
|
832
|
|
|
|
|
|
|
|
|
833
|
|
|
|
|
|
|
B<Note:> |
|
834
|
|
|
|
|
|
|
Correct transaction management can improve performance I<significantly>. Sometimes the improvement can be by a factor of a few thousand. |
|
835
|
|
|
|
|
|
|
Applications should therefore consider to group requests into transactions. This is also true for read only requests. |
|
836
|
|
|
|
|
|
|
|
|
837
|
|
|
|
|
|
|
B<Note:> |
|
838
|
|
|
|
|
|
|
Future versions of this module will depend on L<Data::Identifier>. |
|
839
|
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
B<Note:> |
|
841
|
|
|
|
|
|
|
This module supports SQLite and PostgreSQL (experimental). |
|
842
|
|
|
|
|
|
|
|
|
843
|
|
|
|
|
|
|
=head1 METHODS |
|
844
|
|
|
|
|
|
|
|
|
845
|
|
|
|
|
|
|
=head2 new |
|
846
|
|
|
|
|
|
|
|
|
847
|
|
|
|
|
|
|
my $db = Data::TagDB->new($dsn, ...); |
|
848
|
|
|
|
|
|
|
# or: |
|
849
|
|
|
|
|
|
|
my $db = Data::TagDB->new($dbh); |
|
850
|
|
|
|
|
|
|
|
|
851
|
|
|
|
|
|
|
Returns a new object that can be used for lookups. |
|
852
|
|
|
|
|
|
|
Either an already connected L<DBI> handle can be passed or |
|
853
|
|
|
|
|
|
|
data source that is then passed to L<DBI/connect> internally. |
|
854
|
|
|
|
|
|
|
|
|
855
|
|
|
|
|
|
|
If a open handle is passed, the same restrictions apply as for L</dbh>. |
|
856
|
|
|
|
|
|
|
|
|
857
|
|
|
|
|
|
|
=head2 dbh |
|
858
|
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
my $dbh = $db->dbh; |
|
860
|
|
|
|
|
|
|
|
|
861
|
|
|
|
|
|
|
Returns the current L<DBI> connection. |
|
862
|
|
|
|
|
|
|
|
|
863
|
|
|
|
|
|
|
This connection can be used to call any transaction independent method on the handle. |
|
864
|
|
|
|
|
|
|
It can for example be used to call L<DBI/ping> to keep the connection alive. |
|
865
|
|
|
|
|
|
|
|
|
866
|
|
|
|
|
|
|
If methods are called that depend on the state of the transaction logic |
|
867
|
|
|
|
|
|
|
(such as performing an SELECT or UPDATE) the state of the transaction B<must> be managed via |
|
868
|
|
|
|
|
|
|
this module. See L</begin_work>. |
|
869
|
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
The same holds true for any open handle passed to L</new>. When passed the handle must |
|
871
|
|
|
|
|
|
|
not be in any active transaction and must not be used outside this module to change the transaction |
|
872
|
|
|
|
|
|
|
state of the handle. |
|
873
|
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
It is also wise to avoid interacting with the tables managed by this module. This may result in the |
|
875
|
|
|
|
|
|
|
internal states being in a wrong state. It is however generally safe (but for the restrictions given above) |
|
876
|
|
|
|
|
|
|
to interact with tables outside of the use of this module. |
|
877
|
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
As table names that are in use by this module depend on the version of the schema that is currently active |
|
879
|
|
|
|
|
|
|
(and may change in future) it is most wise to have any custom tables in a seperate namespace of some kind |
|
880
|
|
|
|
|
|
|
(the exact ways to do this may depend on the type of database used). |
|
881
|
|
|
|
|
|
|
|
|
882
|
|
|
|
|
|
|
=head2 disconnect |
|
883
|
|
|
|
|
|
|
|
|
884
|
|
|
|
|
|
|
$db->disconnect |
|
885
|
|
|
|
|
|
|
|
|
886
|
|
|
|
|
|
|
This disconnects from the database backend. It also renders this object useless. |
|
887
|
|
|
|
|
|
|
|
|
888
|
|
|
|
|
|
|
=head2 tag_by_id |
|
889
|
|
|
|
|
|
|
|
|
890
|
|
|
|
|
|
|
my Data::TagDB::Tag $tag = $db->tag_by_id($type => $id); |
|
891
|
|
|
|
|
|
|
# or: |
|
892
|
|
|
|
|
|
|
my Data::TagDB::Tag $tag = $db->tag_by_id($hint => $id); |
|
893
|
|
|
|
|
|
|
# or: |
|
894
|
|
|
|
|
|
|
my Data::Identifier $id = ...; |
|
895
|
|
|
|
|
|
|
my Data::TagDB::Tag $tag = $db->tag_by_id($id); |
|
896
|
|
|
|
|
|
|
# e.g: |
|
897
|
|
|
|
|
|
|
my Data::TagDB::Tag $tag = $db->tag_by_id(uuid => 'abc...'); |
|
898
|
|
|
|
|
|
|
|
|
899
|
|
|
|
|
|
|
Gets a tag by an an identifier of the provided type. The type must be a C<Data::TagDB::Tag> or a |
|
900
|
|
|
|
|
|
|
a string that is a valid hint. |
|
901
|
|
|
|
|
|
|
|
|
902
|
|
|
|
|
|
|
If only argument is provided the argument must be an instance of L<Data::Identifier>. |
|
903
|
|
|
|
|
|
|
|
|
904
|
|
|
|
|
|
|
=head2 tag_by_specification |
|
905
|
|
|
|
|
|
|
|
|
906
|
|
|
|
|
|
|
my Data::TagDB::Tag $tag = $db->tag_by_specification($specification, style => $style [, %opts ]); |
|
907
|
|
|
|
|
|
|
|
|
908
|
|
|
|
|
|
|
Gets a tag by specification according to a style. |
|
909
|
|
|
|
|
|
|
This method is mostly useful to parse user input and find the corresponding tag. |
|
910
|
|
|
|
|
|
|
|
|
911
|
|
|
|
|
|
|
B<Note:> |
|
912
|
|
|
|
|
|
|
This method is experimental. It may change prototype, and behaviour or may be removed in future versions without warning. |
|
913
|
|
|
|
|
|
|
Role matching depends on L<Data::TagDB::Tag/cloudlet> and is subject to its status. |
|
914
|
|
|
|
|
|
|
|
|
915
|
|
|
|
|
|
|
The following styles are supported: |
|
916
|
|
|
|
|
|
|
|
|
917
|
|
|
|
|
|
|
=over |
|
918
|
|
|
|
|
|
|
|
|
919
|
|
|
|
|
|
|
=item C<ise> |
|
920
|
|
|
|
|
|
|
|
|
921
|
|
|
|
|
|
|
The given specification is an UUID, OID, or URI. |
|
922
|
|
|
|
|
|
|
|
|
923
|
|
|
|
|
|
|
=item C<tagpool> |
|
924
|
|
|
|
|
|
|
|
|
925
|
|
|
|
|
|
|
The given specification is in tagpool format. |
|
926
|
|
|
|
|
|
|
Both C<type@tag> and C<tag!> notation is supported (can also be mixed freely). |
|
927
|
|
|
|
|
|
|
|
|
928
|
|
|
|
|
|
|
Parsing interacts with options the same way as tagpool does. |
|
929
|
|
|
|
|
|
|
|
|
930
|
|
|
|
|
|
|
=item C<sirtx> |
|
931
|
|
|
|
|
|
|
|
|
932
|
|
|
|
|
|
|
The given specification is in SIRTX format. |
|
933
|
|
|
|
|
|
|
Currently only I<*local>, I<'number>, I<logical>, and I<type:id> formats are supported. |
|
934
|
|
|
|
|
|
|
There is very limited support for I<%port>, and I<&port>. |
|
935
|
|
|
|
|
|
|
Bracket-escape is only supported for top level. |
|
936
|
|
|
|
|
|
|
|
|
937
|
|
|
|
|
|
|
Supports the options C<sirtx_local_ids>, and C<sirtx_ports>. |
|
938
|
|
|
|
|
|
|
|
|
939
|
|
|
|
|
|
|
=back |
|
940
|
|
|
|
|
|
|
|
|
941
|
|
|
|
|
|
|
The following (all optional) options are supported: |
|
942
|
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
=over |
|
944
|
|
|
|
|
|
|
|
|
945
|
|
|
|
|
|
|
=item C<as_is> |
|
946
|
|
|
|
|
|
|
|
|
947
|
|
|
|
|
|
|
If true, this disables special parsing rules. |
|
948
|
|
|
|
|
|
|
For style C<tagpool> it disables all parsing but the check for UUIDs. |
|
949
|
|
|
|
|
|
|
|
|
950
|
|
|
|
|
|
|
=item C<important> |
|
951
|
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
Requires the tag to be marked important. |
|
953
|
|
|
|
|
|
|
|
|
954
|
|
|
|
|
|
|
=item C<role> |
|
955
|
|
|
|
|
|
|
|
|
956
|
|
|
|
|
|
|
A role the tag is required to have. |
|
957
|
|
|
|
|
|
|
|
|
958
|
|
|
|
|
|
|
=item C<sirtx_local_ids> |
|
959
|
|
|
|
|
|
|
|
|
960
|
|
|
|
|
|
|
An hashref with the local id (without the C<*>) as key and L<Data::TagDB::Tag> as value. |
|
961
|
|
|
|
|
|
|
|
|
962
|
|
|
|
|
|
|
=item C<sirtx_ports> |
|
963
|
|
|
|
|
|
|
|
|
964
|
|
|
|
|
|
|
An arrayref with an even number of elements (key-value pairs). |
|
965
|
|
|
|
|
|
|
Elements with an even index are considered the key (port). |
|
966
|
|
|
|
|
|
|
They are followed by the corresponding (port) value. |
|
967
|
|
|
|
|
|
|
|
|
968
|
|
|
|
|
|
|
All elements must be an instance of L<Data::TagDB::Tag>. |
|
969
|
|
|
|
|
|
|
|
|
970
|
|
|
|
|
|
|
=back |
|
971
|
|
|
|
|
|
|
|
|
972
|
|
|
|
|
|
|
=head2 relation |
|
973
|
|
|
|
|
|
|
|
|
974
|
|
|
|
|
|
|
my Data::TagDB::Iterator $iter = $db->relation(...); |
|
975
|
|
|
|
|
|
|
|
|
976
|
|
|
|
|
|
|
Returns an iterator for relations. |
|
977
|
|
|
|
|
|
|
The following keys can be used to filter the list. All must be L<Data::TagDB::Tag> or an array ref of them objects: |
|
978
|
|
|
|
|
|
|
C<tag>, |
|
979
|
|
|
|
|
|
|
C<relation>, |
|
980
|
|
|
|
|
|
|
C<context>, |
|
981
|
|
|
|
|
|
|
C<filter>, and |
|
982
|
|
|
|
|
|
|
C<related>. |
|
983
|
|
|
|
|
|
|
Each may be prefixed with C<no_> for negative filtering. |
|
984
|
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
=head2 metadata |
|
986
|
|
|
|
|
|
|
|
|
987
|
|
|
|
|
|
|
my Data::TagDB::Iterator $iter = $db->metadata(...); |
|
988
|
|
|
|
|
|
|
|
|
989
|
|
|
|
|
|
|
Returns an iterator for relations. |
|
990
|
|
|
|
|
|
|
The following keys can be used to filter the list. All must be L<Data::TagDB::Tag> or an array ref of them objects: |
|
991
|
|
|
|
|
|
|
C<tag>, |
|
992
|
|
|
|
|
|
|
C<relation>, |
|
993
|
|
|
|
|
|
|
C<context>, |
|
994
|
|
|
|
|
|
|
C<type>, and |
|
995
|
|
|
|
|
|
|
C<encoding>. |
|
996
|
|
|
|
|
|
|
Each may be prefixed with C<no_> for negative filtering. |
|
997
|
|
|
|
|
|
|
|
|
998
|
|
|
|
|
|
|
Additionally C<data_raw> can be used to filter for a data value. |
|
999
|
|
|
|
|
|
|
|
|
1000
|
|
|
|
|
|
|
=head2 link |
|
1001
|
|
|
|
|
|
|
|
|
1002
|
|
|
|
|
|
|
my Data::TagDB::Iterator $iter = $db->link(...); |
|
1003
|
|
|
|
|
|
|
|
|
1004
|
|
|
|
|
|
|
This combines L</relation>, and L</metadata>. An iterator is returned that lists both metadata, and relations (in any order). |
|
1005
|
|
|
|
|
|
|
The common subset of filters can be used. Namely: |
|
1006
|
|
|
|
|
|
|
C<tag>, |
|
1007
|
|
|
|
|
|
|
C<relation>, and |
|
1008
|
|
|
|
|
|
|
C<context>. |
|
1009
|
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
=head2 wk |
|
1011
|
|
|
|
|
|
|
|
|
1012
|
|
|
|
|
|
|
my Data::TagDB::WellKnown $tag = $db->wk; |
|
1013
|
|
|
|
|
|
|
my Data::TagDB::Tag $tag = $wk->...; |
|
1014
|
|
|
|
|
|
|
# e.g.: |
|
1015
|
|
|
|
|
|
|
my Data::TagDB::Tag $asi = $db->wk->also_shares_identifier; |
|
1016
|
|
|
|
|
|
|
|
|
1017
|
|
|
|
|
|
|
Returns a dictionary of well known tags. |
|
1018
|
|
|
|
|
|
|
|
|
1019
|
|
|
|
|
|
|
=head2 register_decoder |
|
1020
|
|
|
|
|
|
|
|
|
1021
|
|
|
|
|
|
|
$db->register_decoder($type, $encoding, sub { ... }); |
|
1022
|
|
|
|
|
|
|
|
|
1023
|
|
|
|
|
|
|
Registers a decoder for a given type and encoding. Both C<$type>, and C<$encoding> |
|
1024
|
|
|
|
|
|
|
must be L<Data::TagDB::Tag>. |
|
1025
|
|
|
|
|
|
|
|
|
1026
|
|
|
|
|
|
|
=head2 create_tag |
|
1027
|
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
my Data::TagDB::Tag $tag = $db->create_tag([$type => $value, ...], [$type => $value, ...]); |
|
1029
|
|
|
|
|
|
|
# or: |
|
1030
|
|
|
|
|
|
|
my Data::Identifier $id = ...; |
|
1031
|
|
|
|
|
|
|
my Data::Identifier $extra = ...; |
|
1032
|
|
|
|
|
|
|
my Data::TagDB::Tag $tag = $db->create_tag($id, [ $extra ]); |
|
1033
|
|
|
|
|
|
|
|
|
1034
|
|
|
|
|
|
|
Create a tag (or return it if it already exists). Takes two lists if type-identifier pairs. |
|
1035
|
|
|
|
|
|
|
The first list is the list of identifiers that uniquely identify the tag (e.g. an UUID). |
|
1036
|
|
|
|
|
|
|
The second list contains additional, non unique identifiers (e.g. tagnames) and is optional. |
|
1037
|
|
|
|
|
|
|
|
|
1038
|
|
|
|
|
|
|
If the tag does not exist it is created. Once it exists all identifiers added (for already existing tags missing identifiers are added). |
|
1039
|
|
|
|
|
|
|
|
|
1040
|
|
|
|
|
|
|
Each list can be replaced by a single instance of L<Data::Identifier>. |
|
1041
|
|
|
|
|
|
|
|
|
1042
|
|
|
|
|
|
|
=head2 create_metadata |
|
1043
|
|
|
|
|
|
|
|
|
1044
|
|
|
|
|
|
|
my Data::TagDB::Metadata $metadata = $db->create_metadata( |
|
1045
|
|
|
|
|
|
|
tag => $tag, # required |
|
1046
|
|
|
|
|
|
|
relation => $relation, # required |
|
1047
|
|
|
|
|
|
|
context => $context, |
|
1048
|
|
|
|
|
|
|
type => $type, |
|
1049
|
|
|
|
|
|
|
encoding => $encoding, |
|
1050
|
|
|
|
|
|
|
data_raw => $raw, # required |
|
1051
|
|
|
|
|
|
|
); |
|
1052
|
|
|
|
|
|
|
|
|
1053
|
|
|
|
|
|
|
Create a metadata entry if it does not yet exist. Returns it once created. |
|
1054
|
|
|
|
|
|
|
|
|
1055
|
|
|
|
|
|
|
=head2 create_relation |
|
1056
|
|
|
|
|
|
|
|
|
1057
|
|
|
|
|
|
|
my Data::TagDB::Relation $relation = $db->create_relation( |
|
1058
|
|
|
|
|
|
|
tag => $tag, # required |
|
1059
|
|
|
|
|
|
|
relation => $relation, # required |
|
1060
|
|
|
|
|
|
|
related => $related, # required |
|
1061
|
|
|
|
|
|
|
context => $context, |
|
1062
|
|
|
|
|
|
|
filter => $filter, |
|
1063
|
|
|
|
|
|
|
); |
|
1064
|
|
|
|
|
|
|
|
|
1065
|
|
|
|
|
|
|
Creates a relation (if it does not yet exist) and returns it. |
|
1066
|
|
|
|
|
|
|
|
|
1067
|
|
|
|
|
|
|
=head2 create_cache |
|
1068
|
|
|
|
|
|
|
|
|
1069
|
|
|
|
|
|
|
my Data::TagDB::Cache $cache = $db->create_cache; |
|
1070
|
|
|
|
|
|
|
|
|
1071
|
|
|
|
|
|
|
Create a new cache object every time this is called. |
|
1072
|
|
|
|
|
|
|
Cache objects can be used to speed up processing. |
|
1073
|
|
|
|
|
|
|
See L<Data::TagDB::Cache> for details. |
|
1074
|
|
|
|
|
|
|
|
|
1075
|
|
|
|
|
|
|
=head2 migration |
|
1076
|
|
|
|
|
|
|
|
|
1077
|
|
|
|
|
|
|
$db->migration->upgrade; |
|
1078
|
|
|
|
|
|
|
|
|
1079
|
|
|
|
|
|
|
Get a migration object. This is mostly used for upgrading the database schema to the |
|
1080
|
|
|
|
|
|
|
current one. It is recommended to perform upgrades for long running processes. |
|
1081
|
|
|
|
|
|
|
For short running processes this can increase the startup time. |
|
1082
|
|
|
|
|
|
|
|
|
1083
|
|
|
|
|
|
|
See also L<Data::TagDB::Migration>. |
|
1084
|
|
|
|
|
|
|
|
|
1085
|
|
|
|
|
|
|
=head2 factory |
|
1086
|
|
|
|
|
|
|
|
|
1087
|
|
|
|
|
|
|
my Data::TagDB::Factory $factory = $db->factory; |
|
1088
|
|
|
|
|
|
|
|
|
1089
|
|
|
|
|
|
|
Get a factory object used to create tags. |
|
1090
|
|
|
|
|
|
|
See also L<Data::TagDB::Factory> for details. |
|
1091
|
|
|
|
|
|
|
|
|
1092
|
|
|
|
|
|
|
=head2 exporter |
|
1093
|
|
|
|
|
|
|
|
|
1094
|
|
|
|
|
|
|
my Data::TagDB::Exporter $exporter = $db->exporter($target, %opts); |
|
1095
|
|
|
|
|
|
|
|
|
1096
|
|
|
|
|
|
|
Create a new exporter. C<$target> must be a open file handle (that supports seeking) |
|
1097
|
|
|
|
|
|
|
or a filename. |
|
1098
|
|
|
|
|
|
|
|
|
1099
|
|
|
|
|
|
|
See also L<Data::TagDB::Exporter>. |
|
1100
|
|
|
|
|
|
|
|
|
1101
|
|
|
|
|
|
|
The following options (all optional) are defined: |
|
1102
|
|
|
|
|
|
|
|
|
1103
|
|
|
|
|
|
|
=over |
|
1104
|
|
|
|
|
|
|
|
|
1105
|
|
|
|
|
|
|
=item C<format> |
|
1106
|
|
|
|
|
|
|
|
|
1107
|
|
|
|
|
|
|
The format to use. This can be L<Data::TagDB::Tag>, a L<Data::Identfier>, or a raw ISE string. |
|
1108
|
|
|
|
|
|
|
|
|
1109
|
|
|
|
|
|
|
The default is I<tagpool-source-format> (C<e5da6a39-46d5-48a9-b174-5c26008e208e>). |
|
1110
|
|
|
|
|
|
|
|
|
1111
|
|
|
|
|
|
|
=back |
|
1112
|
|
|
|
|
|
|
|
|
1113
|
|
|
|
|
|
|
=head2 begin_work, commit, rollback |
|
1114
|
|
|
|
|
|
|
|
|
1115
|
|
|
|
|
|
|
$db->begin_work; |
|
1116
|
|
|
|
|
|
|
# ... |
|
1117
|
|
|
|
|
|
|
$db->commit; |
|
1118
|
|
|
|
|
|
|
# or: |
|
1119
|
|
|
|
|
|
|
$db->rollback; |
|
1120
|
|
|
|
|
|
|
|
|
1121
|
|
|
|
|
|
|
Those methods are provided as proxy to L<DBI>'s. |
|
1122
|
|
|
|
|
|
|
The correct use of transactions can improve the speed (both for reading and writing) |
|
1123
|
|
|
|
|
|
|
significantly. Specifically tag databases are subject to many improvements of correct transaction |
|
1124
|
|
|
|
|
|
|
mangement. |
|
1125
|
|
|
|
|
|
|
|
|
1126
|
|
|
|
|
|
|
B<Note:> |
|
1127
|
|
|
|
|
|
|
For each call to C<begin_work> there must be a matching call to C<commit> or C<rollback>. |
|
1128
|
|
|
|
|
|
|
This is important as this API will keep track of transactions internally. |
|
1129
|
|
|
|
|
|
|
|
|
1130
|
|
|
|
|
|
|
B<Note:> |
|
1131
|
|
|
|
|
|
|
A call to C<begin_work> may or may not fail if another transaction is already in process. |
|
1132
|
|
|
|
|
|
|
This may depend on the type of database used. |
|
1133
|
|
|
|
|
|
|
|
|
1134
|
|
|
|
|
|
|
B<Note:> |
|
1135
|
|
|
|
|
|
|
The return value of those methods is undefined. On error they will C<die>. |
|
1136
|
|
|
|
|
|
|
|
|
1137
|
|
|
|
|
|
|
B<Note:> |
|
1138
|
|
|
|
|
|
|
These methods are mutually exclusive with the use of L</in_transaction> at this time. |
|
1139
|
|
|
|
|
|
|
However, the use of L</in_transaction> is recommended. |
|
1140
|
|
|
|
|
|
|
|
|
1141
|
|
|
|
|
|
|
For details see also: L<DBI/begin_work>, L<DBI/commit>, L<DBI/rollback>. |
|
1142
|
|
|
|
|
|
|
|
|
1143
|
|
|
|
|
|
|
=head2 in_transaction |
|
1144
|
|
|
|
|
|
|
|
|
1145
|
|
|
|
|
|
|
$db->in_transaction(ro => sub { ....}); |
|
1146
|
|
|
|
|
|
|
# or: |
|
1147
|
|
|
|
|
|
|
$db->in_transaction(rw => sub { ....}); |
|
1148
|
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
Runs a block of code (a subref) inside a transaction. |
|
1150
|
|
|
|
|
|
|
|
|
1151
|
|
|
|
|
|
|
The passed block is run in a transaction. The transaction is commited after the code finishes. |
|
1152
|
|
|
|
|
|
|
|
|
1153
|
|
|
|
|
|
|
The type of the transaction can be C<ro> (read only) or C<rw> (read-write). |
|
1154
|
|
|
|
|
|
|
The module may optimise based on this information. |
|
1155
|
|
|
|
|
|
|
If a write operation is performed in a transaction that is marked C<ro> the behaviour is unspecified. |
|
1156
|
|
|
|
|
|
|
|
|
1157
|
|
|
|
|
|
|
In contrast to L</begin_work> and L</commit> calls to this method can be stacked freely. |
|
1158
|
|
|
|
|
|
|
For example the following is valid: |
|
1159
|
|
|
|
|
|
|
|
|
1160
|
|
|
|
|
|
|
$db->in_transaction(ro => sub { |
|
1161
|
|
|
|
|
|
|
# do some read... |
|
1162
|
|
|
|
|
|
|
$db->in_transaction(rw => sub { |
|
1163
|
|
|
|
|
|
|
# do some write... |
|
1164
|
|
|
|
|
|
|
}); |
|
1165
|
|
|
|
|
|
|
# do more reading, writing is invalid here |
|
1166
|
|
|
|
|
|
|
}); |
|
1167
|
|
|
|
|
|
|
|
|
1168
|
|
|
|
|
|
|
B<Note:> |
|
1169
|
|
|
|
|
|
|
If the code C<die>s the error is ignored. The transaction is still commited. |
|
1170
|
|
|
|
|
|
|
If the code wants to perform rollback in case it fails this function might not be the one to use. |
|
1171
|
|
|
|
|
|
|
|
|
1172
|
|
|
|
|
|
|
B<Note:> |
|
1173
|
|
|
|
|
|
|
Data written might only be visible to other handles of the same database once I<all> |
|
1174
|
|
|
|
|
|
|
transactions have been finished. |
|
1175
|
|
|
|
|
|
|
|
|
1176
|
|
|
|
|
|
|
B<Note:> |
|
1177
|
|
|
|
|
|
|
This method is mutually exclusive with the use of L</begin_work> at this time. |
|
1178
|
|
|
|
|
|
|
|
|
1179
|
|
|
|
|
|
|
=head2 tag_by_hint |
|
1180
|
|
|
|
|
|
|
|
|
1181
|
|
|
|
|
|
|
my Data::TagDB::Tag $tag = $db->tag_by_hint($hint); |
|
1182
|
|
|
|
|
|
|
|
|
1183
|
|
|
|
|
|
|
Get a tag by hint. What hints are supported depends on what is stored in the database's hint table. |
|
1184
|
|
|
|
|
|
|
|
|
1185
|
|
|
|
|
|
|
=head1 AUTHOR |
|
1186
|
|
|
|
|
|
|
|
|
1187
|
|
|
|
|
|
|
Philipp Schafft <lion@cpan.org> |
|
1188
|
|
|
|
|
|
|
|
|
1189
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
|
1190
|
|
|
|
|
|
|
|
|
1191
|
|
|
|
|
|
|
This software is Copyright (c) 2024-2025 by Philipp Schafft <lion@cpan.org>. |
|
1192
|
|
|
|
|
|
|
|
|
1193
|
|
|
|
|
|
|
This is free software, licensed under: |
|
1194
|
|
|
|
|
|
|
|
|
1195
|
|
|
|
|
|
|
The Artistic License 2.0 (GPL Compatible) |
|
1196
|
|
|
|
|
|
|
|
|
1197
|
|
|
|
|
|
|
=cut |