| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
=head1 NAME |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
SQLite::VirtualTable::Pivot -- use SQLite's virtual tables to represent pivot tables. |
|
4
|
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
$ export SQLITE_CURRENT_DB=/tmp/foo.db |
|
8
|
|
|
|
|
|
|
sqlite3 $SQLITE_CURRENT_DB |
|
9
|
|
|
|
|
|
|
sqlite> .load perlvtab.so |
|
10
|
|
|
|
|
|
|
sqlite> create table object_attributes (id integer, name varchar, value integer); |
|
11
|
|
|
|
|
|
|
sqlite> insert into object_attributes values ( 1, "length", 20 ); |
|
12
|
|
|
|
|
|
|
sqlite> insert into object_attributes values ( 1, "color", "red" ); |
|
13
|
|
|
|
|
|
|
sqlite> create virtual table object_pivot using perl |
|
14
|
|
|
|
|
|
|
("SQLite::VirtualTable::Pivot", "object_attributes" ); |
|
15
|
|
|
|
|
|
|
sqlite> select * from object_pivot; |
|
16
|
|
|
|
|
|
|
id|color|length |
|
17
|
|
|
|
|
|
|
1|red|20 |
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
A pivot table is a table in which the distinct row values of a column |
|
22
|
|
|
|
|
|
|
in one table are used as the names of the columns in another table. |
|
23
|
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
Here's an example: |
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
Given this table : |
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
Student Subject Grade |
|
29
|
|
|
|
|
|
|
------- ------- ----- |
|
30
|
|
|
|
|
|
|
Joe Reading A |
|
31
|
|
|
|
|
|
|
Joe Writing B |
|
32
|
|
|
|
|
|
|
Joe Arithmetic C |
|
33
|
|
|
|
|
|
|
Mary Reading B- |
|
34
|
|
|
|
|
|
|
Mary Writing A+ |
|
35
|
|
|
|
|
|
|
Mary Arithmetic C+ |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
A pivot table created using the columns "Student" and "Subject" |
|
38
|
|
|
|
|
|
|
and the value "Grade" would yield : |
|
39
|
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
Student Arithmetic Reading Writing |
|
41
|
|
|
|
|
|
|
------- ---------- ------- ---------- |
|
42
|
|
|
|
|
|
|
Joe C A B |
|
43
|
|
|
|
|
|
|
Mary C+ B- A+ |
|
44
|
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
To create a table, use the following syntax : |
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
create virtual table object_pivot using perl |
|
48
|
|
|
|
|
|
|
("SQLite::VirtualTable::Pivot", "base_table" ); |
|
49
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
To specify the three columns, use : |
|
51
|
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
create virtual table object_pivot using perl |
|
53
|
|
|
|
|
|
|
("SQLite::VirtualTable::Pivot", "base_table", |
|
54
|
|
|
|
|
|
|
"pivot_row", "pivot_column", "pivot_value" ); |
|
55
|
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
where pivot_row, pivot_column and pivot_value are three columns |
|
57
|
|
|
|
|
|
|
in the base_table. The distinct values of pivot_column will be |
|
58
|
|
|
|
|
|
|
the names of the new columns in the pivot table. (The values may |
|
59
|
|
|
|
|
|
|
be sanitized to create valid column names.) |
|
60
|
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
If any of the three columns are foreign keys, these may be |
|
62
|
|
|
|
|
|
|
collapsed in the pivot table, as described below. |
|
63
|
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
The list of distinct columns is calculated the first |
|
65
|
|
|
|
|
|
|
time a pivot table is used (or created) in a database session. |
|
66
|
|
|
|
|
|
|
So, if the list changes, you may need to re-connect. |
|
67
|
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
=head1 Entity-Atribute-Value models |
|
69
|
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
The Entity-Attribute-Value model is a representation of data in |
|
71
|
|
|
|
|
|
|
a table containing three columns representing an entity, an attribute, |
|
72
|
|
|
|
|
|
|
and a value. For instance : |
|
73
|
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
Entity Attribute Value |
|
75
|
|
|
|
|
|
|
------ --------- ----- |
|
76
|
|
|
|
|
|
|
1 color red |
|
77
|
|
|
|
|
|
|
1 length 20 |
|
78
|
|
|
|
|
|
|
2 color blue |
|
79
|
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
To reduce redundancy or to constrain the possible attributes/values, |
|
81
|
|
|
|
|
|
|
some or all of the three columns may be foreign keys. Consider for |
|
82
|
|
|
|
|
|
|
instance, the following : |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
create table entities ( |
|
85
|
|
|
|
|
|
|
id integer primary key, |
|
86
|
|
|
|
|
|
|
entity varchar, |
|
87
|
|
|
|
|
|
|
unique (entity) ); |
|
88
|
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
create table attributes ( |
|
90
|
|
|
|
|
|
|
id integer primary key, |
|
91
|
|
|
|
|
|
|
attribute varchar, |
|
92
|
|
|
|
|
|
|
unique (attribute) ); |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
create table value_s ( |
|
95
|
|
|
|
|
|
|
id integer primary key, |
|
96
|
|
|
|
|
|
|
value integer, -- nb: "integer" is only the column affinity |
|
97
|
|
|
|
|
|
|
unique (value) ); |
|
98
|
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
create table eav ( |
|
100
|
|
|
|
|
|
|
entity integer references entities(id), |
|
101
|
|
|
|
|
|
|
attribute integer references attributes(id), |
|
102
|
|
|
|
|
|
|
value integer references value_s(id), |
|
103
|
|
|
|
|
|
|
primary key (entity,attribute) |
|
104
|
|
|
|
|
|
|
); |
|
105
|
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
Then the foreign keys may be "flattened" into the pivot table |
|
107
|
|
|
|
|
|
|
by using this SQL : |
|
108
|
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
create virtual table |
|
110
|
|
|
|
|
|
|
eav_pivot using perl ("SQLite::VirtualTable::Pivot", |
|
111
|
|
|
|
|
|
|
"eav", |
|
112
|
|
|
|
|
|
|
"entity->entity(id).entity", |
|
113
|
|
|
|
|
|
|
"attribute->attributes(id).attribute", |
|
114
|
|
|
|
|
|
|
"value->value_s(id).value" |
|
115
|
|
|
|
|
|
|
); |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
Then the columns in eav_pivot would be the entries in |
|
118
|
|
|
|
|
|
|
attributes.attribute corresponding to the distinct |
|
119
|
|
|
|
|
|
|
values in eav.attribute. |
|
120
|
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
Moreover, queries against the pivot table will do the right |
|
122
|
|
|
|
|
|
|
thing, in the sense that restrictions will use the values in the |
|
123
|
|
|
|
|
|
|
value_s table, not in the eav table. |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
=head1 EXAMPLE |
|
126
|
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
create table students (student, subject, grade, primary key (student,subject)); |
|
128
|
|
|
|
|
|
|
insert into students values ("Joe", "Reading", "A"); |
|
129
|
|
|
|
|
|
|
insert into students values ("Joe", "Writing", "B"); |
|
130
|
|
|
|
|
|
|
insert into students values ("Joe", "Arithmetic", "C"); |
|
131
|
|
|
|
|
|
|
insert into students values ("Mary", "Reading", "B-"); |
|
132
|
|
|
|
|
|
|
insert into students values ("Mary", "Writing", "A+"); |
|
133
|
|
|
|
|
|
|
insert into students values ("Mary", "Arithmetic", "C+"); |
|
134
|
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
select load_extension("perlvtab.so"); |
|
136
|
|
|
|
|
|
|
create virtual table roster using perl ("SQLite::VirtualTable::Pivot", "students", "student", "subject", "grade"); |
|
137
|
|
|
|
|
|
|
select * from roster; |
|
138
|
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
Student Reading Writing Arithmetic |
|
140
|
|
|
|
|
|
|
------- ------- ------- ---------- |
|
141
|
|
|
|
|
|
|
Joe A B C |
|
142
|
|
|
|
|
|
|
Mary B- A+ C+ |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
select student from roster where writing = "A+"; |
|
145
|
|
|
|
|
|
|
Mary |
|
146
|
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
=head1 FUNCTIONS (called by sqlite, see SQLite::VirtualTable) |
|
148
|
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
=cut |
|
150
|
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
package SQLite::VirtualTable::Pivot; |
|
152
|
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
# from CPAN |
|
154
|
1
|
|
|
1
|
|
23949
|
use DBI; |
|
|
1
|
|
|
|
|
18735
|
|
|
|
1
|
|
|
|
|
75
|
|
|
155
|
1
|
|
|
1
|
|
1055
|
use DBIx::Simple; |
|
|
1
|
|
|
|
|
7437
|
|
|
|
1
|
|
|
|
|
33
|
|
|
156
|
1
|
|
|
1
|
|
1009
|
use Data::Dumper; |
|
|
1
|
|
|
|
|
7651
|
|
|
|
1
|
|
|
|
|
99
|
|
|
157
|
1
|
|
|
1
|
|
10
|
use Scalar::Util qw/looks_like_number/; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
92
|
|
|
158
|
1
|
|
|
1
|
|
524
|
use SQLite::VirtualTable::Util qw/unescape/; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
# base modules |
|
161
|
|
|
|
|
|
|
use base 'SQLite::VirtualTable'; |
|
162
|
|
|
|
|
|
|
use base 'Class::Accessor::Contextual'; |
|
163
|
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
# local module |
|
165
|
|
|
|
|
|
|
use SQLite::VirtualTable::Pivot::Cursor; |
|
166
|
|
|
|
|
|
|
use strict; |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
our $VERSION = 0.02; |
|
169
|
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
# Create r/w accessors for everything that we store in the class hash |
|
171
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(qw| table |); # base_table name and distinct values |
|
172
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(qw| columns |); # distinct values in base_table.$pivot_row |
|
173
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(qw| vcolumns |); # valid column names based on the above |
|
174
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(qw| indexes counts |); # populated by BEST_INDEX, used by FILTER |
|
175
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(qw| pivot_row pivot_row_ref |); # entity (in EAV) + fk info |
|
176
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(qw| pivot_column pivot_column_ref |); # attribute + fk info |
|
177
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(qw| pivot_value pivot_value_ref |); # value + fk info |
|
178
|
|
|
|
|
|
|
__PACKAGE__->mk_accessors(qw| pivot_row_type |); # column affinity for entity |
|
179
|
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
# We need to use an env variable until DBD::SQLite + SQLite::VirtualTable |
|
181
|
|
|
|
|
|
|
# work together to pass one to CREATE() |
|
182
|
|
|
|
|
|
|
our $dbfile = $ENV{SQLITE_CURRENT_DB} or die "please set SQLITE_CURRENT_DB"; |
|
183
|
|
|
|
|
|
|
our $db; # handle: DBIx::Simple object |
|
184
|
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
# debug setup |
|
186
|
|
|
|
|
|
|
#$ENV{TRACE} = 1; |
|
187
|
|
|
|
|
|
|
#$ENV{DEBUG} = 1; |
|
188
|
|
|
|
|
|
|
sub debug($) { return unless $ENV{DEBUG}; print STDERR "# $_[0]\n"; } |
|
189
|
|
|
|
|
|
|
sub trace($) { return unless $ENV{TRACE}; print STDERR "# $_[0]\n"; } |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
# Initialize the database handle. Send force => 1 to force a reconnect |
|
192
|
|
|
|
|
|
|
sub _init_db { |
|
193
|
|
|
|
|
|
|
my %args = @_; |
|
194
|
|
|
|
|
|
|
our $db; |
|
195
|
|
|
|
|
|
|
return if defined($db) && !$args{force}; |
|
196
|
|
|
|
|
|
|
debug "connect to $dbfile"; |
|
197
|
|
|
|
|
|
|
$db = DBIx::Simple->connect( "dbi:SQLite:dbname=$dbfile", "", "" ) |
|
198
|
|
|
|
|
|
|
or die DBIx::Simple->error; |
|
199
|
|
|
|
|
|
|
$db->dbh->do("PRAGMA temp_store = 2"); # use in-memory temp tables |
|
200
|
|
|
|
|
|
|
} |
|
201
|
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
# Parse the string indicating a foreign key relationship in the base_table. |
|
203
|
|
|
|
|
|
|
# Given "entity->entity_ref(id).value", |
|
204
|
|
|
|
|
|
|
# return ("entity" , { table=>"entity_ref", child_key => "id", child_label => "value"} ). |
|
205
|
|
|
|
|
|
|
sub _parse_refspec { |
|
206
|
|
|
|
|
|
|
my $str = shift; |
|
207
|
|
|
|
|
|
|
$str =~ /^(.*)->(.*)\((.*)\)\.(.*)$/ |
|
208
|
|
|
|
|
|
|
and return ( $1, { table => $2, child_key => $3, child_label => $4 } ); |
|
209
|
|
|
|
|
|
|
return $str; |
|
210
|
|
|
|
|
|
|
} |
|
211
|
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
=head1 CREATE (constructor) |
|
213
|
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
Arguments : |
|
215
|
|
|
|
|
|
|
module : "perl", |
|
216
|
|
|
|
|
|
|
caller : "main" |
|
217
|
|
|
|
|
|
|
virtual_table : the name of the table being created |
|
218
|
|
|
|
|
|
|
base_table : the table being pivoted |
|
219
|
|
|
|
|
|
|
@pivot_columns (optional) : entity, attribute, value |
|
220
|
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
Returns : |
|
222
|
|
|
|
|
|
|
A new SQLite::VirtualTable::Pivot object. |
|
223
|
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
Description : |
|
225
|
|
|
|
|
|
|
Create a new SQLite::VirtualTable::Pivot object. The base_table |
|
226
|
|
|
|
|
|
|
is the table to be pivoted. If this table contains only three |
|
227
|
|
|
|
|
|
|
columns, then they will be used in order as the pivot_row, |
|
228
|
|
|
|
|
|
|
pivot_column, and pivot_value columns (aka entity, attribute, value). |
|
229
|
|
|
|
|
|
|
Alternatively, these columns may be specified in the create |
|
230
|
|
|
|
|
|
|
statement by passing them as parameters. If one of the values |
|
231
|
|
|
|
|
|
|
is a foreign key and the pivot table should instead use a column |
|
232
|
|
|
|
|
|
|
in the child table, that may be specified using the following |
|
233
|
|
|
|
|
|
|
notation : |
|
234
|
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
base_table_column->child_table(child_key).child_column_to_use |
|
236
|
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
If a column name contains a space, then the portion after the |
|
238
|
|
|
|
|
|
|
space should be the column affinity. |
|
239
|
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
Examples : |
|
241
|
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
CREATE VIRTUAL TABLE pivot_table USING perl |
|
243
|
|
|
|
|
|
|
("SQLite::VirtualTable::Pivot","base_table" ); |
|
244
|
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
CREATE VIRTUAL TABLE pivot_table USING perl |
|
246
|
|
|
|
|
|
|
("SQLite::VirtualTable::Pivot","base_table", |
|
247
|
|
|
|
|
|
|
"entity","attribute","value"); |
|
248
|
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
CREATE VIRTUAL TABLE pivot_table USING perl |
|
250
|
|
|
|
|
|
|
("SQLite::VirtualTable::Pivot","base_table", |
|
251
|
|
|
|
|
|
|
"entity integer","attribute varchar","value integer"); |
|
252
|
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
CREATE VIRTUAL TABLE pivot_table USING perl |
|
254
|
|
|
|
|
|
|
("SQLite::VirtualTable::Pivot","base_table", |
|
255
|
|
|
|
|
|
|
"entty", |
|
256
|
|
|
|
|
|
|
"attribute->attribute_lookup(id).attr", |
|
257
|
|
|
|
|
|
|
"value->value_lookup(id).value" ); |
|
258
|
|
|
|
|
|
|
=cut |
|
259
|
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
sub CREATE { |
|
261
|
|
|
|
|
|
|
my ( $class, $module, $caller, $virtual_table, $base_table, @pivot_columns ) = @_; |
|
262
|
|
|
|
|
|
|
trace "(CREATE, got @_)"; |
|
263
|
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
# connect |
|
265
|
|
|
|
|
|
|
_init_db(); |
|
266
|
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
# Get the base_table and its metadata. Parse the sql used to create it. |
|
268
|
|
|
|
|
|
|
$base_table = unescape($base_table); |
|
269
|
|
|
|
|
|
|
my ($createsql) = |
|
270
|
|
|
|
|
|
|
$db->select( 'sqlite_master', ['sql'], { name => $base_table } )->list |
|
271
|
|
|
|
|
|
|
or die "Could not find table '$base_table' " . $db->error; |
|
272
|
|
|
|
|
|
|
$createsql =~ s/^[^\(]*\(//; # remove leading |
|
273
|
|
|
|
|
|
|
$createsql =~ s/\)[^\)]*$//; # and trailing "CREATE" declaration, to get columns |
|
274
|
|
|
|
|
|
|
my @columns_and_contraints = split /,/, $createsql; |
|
275
|
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
# Set up the pivot_row (entity), pivot_column (attribute) and |
|
277
|
|
|
|
|
|
|
# pivot_value (value) columns, including foreign key specifications. |
|
278
|
|
|
|
|
|
|
my ($pivot_row, $pivot_row_type, $pivot_column, $pivot_value ); |
|
279
|
|
|
|
|
|
|
my ($pivot_row_ref, $pivot_column_ref, $pivot_value_ref); |
|
280
|
|
|
|
|
|
|
if (@pivot_columns == 3) { |
|
281
|
|
|
|
|
|
|
($pivot_row, $pivot_column, $pivot_value ) = map unescape($_), @pivot_columns; |
|
282
|
|
|
|
|
|
|
if ($pivot_row =~ / /) { |
|
283
|
|
|
|
|
|
|
($pivot_row,$pivot_row_type) = split / /, $pivot_row; |
|
284
|
|
|
|
|
|
|
} |
|
285
|
|
|
|
|
|
|
($pivot_row ,$pivot_row_ref) = _parse_refspec($pivot_row); |
|
286
|
|
|
|
|
|
|
($pivot_column,$pivot_column_ref) = _parse_refspec($pivot_column); |
|
287
|
|
|
|
|
|
|
($pivot_value ,$pivot_value_ref) = _parse_refspec($pivot_value); |
|
288
|
|
|
|
|
|
|
} else { |
|
289
|
|
|
|
|
|
|
($pivot_row, $pivot_column, $pivot_value ) = @columns_and_contraints; |
|
290
|
|
|
|
|
|
|
($pivot_row_type) = $pivot_row =~ /^\s*\S* (.*)$/; |
|
291
|
|
|
|
|
|
|
} |
|
292
|
|
|
|
|
|
|
for my $col ($pivot_row, $pivot_column, $pivot_value ) { |
|
293
|
|
|
|
|
|
|
$col =~ s/^\s*//; |
|
294
|
|
|
|
|
|
|
$col =~ s/ .*$//; |
|
295
|
|
|
|
|
|
|
next if grep /$col/i, @columns_and_contraints; |
|
296
|
|
|
|
|
|
|
warn "could not find $col in columns for $base_table\n"; |
|
297
|
|
|
|
|
|
|
} |
|
298
|
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
# Now compute the distinct values of pivot_row (attribute). |
|
300
|
|
|
|
|
|
|
debug "pivot_column (attribute) is $pivot_column"; |
|
301
|
|
|
|
|
|
|
my @columns = ( |
|
302
|
|
|
|
|
|
|
$pivot_row, |
|
303
|
|
|
|
|
|
|
$db->query( sprintf( |
|
304
|
|
|
|
|
|
|
"SELECT DISTINCT(%s) FROM %s", |
|
305
|
|
|
|
|
|
|
$pivot_column, $base_table))->flat |
|
306
|
|
|
|
|
|
|
); |
|
307
|
|
|
|
|
|
|
debug "distinct values for $pivot_column in $base_table are @columns"; |
|
308
|
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
my @vcolumns = @columns; # virtual table column names |
|
310
|
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
# Maybe apply foreign key transform to make vcolumns. |
|
312
|
|
|
|
|
|
|
if ($pivot_column_ref) { |
|
313
|
|
|
|
|
|
|
@vcolumns = ($vcolumns[0]); |
|
314
|
|
|
|
|
|
|
for my $c (@columns) { |
|
315
|
|
|
|
|
|
|
my ($next) = $db->select( |
|
316
|
|
|
|
|
|
|
$pivot_column_ref->{table}, |
|
317
|
|
|
|
|
|
|
$pivot_column_ref->{child_label}, |
|
318
|
|
|
|
|
|
|
{ $pivot_column_ref->{child_key} => $c } |
|
319
|
|
|
|
|
|
|
)->flat or next; |
|
320
|
|
|
|
|
|
|
push @vcolumns, $next; |
|
321
|
|
|
|
|
|
|
} |
|
322
|
|
|
|
|
|
|
} |
|
323
|
|
|
|
|
|
|
# Ensure that they are valid sqlite column names |
|
324
|
|
|
|
|
|
|
for (@vcolumns) { |
|
325
|
|
|
|
|
|
|
tr/a-zA-Z0-9_//dc; |
|
326
|
|
|
|
|
|
|
$_ = "$pivot_column\_$_" unless $_=~/^[a-zA-Z]/; |
|
327
|
|
|
|
|
|
|
} |
|
328
|
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
$pivot_row_type ||= "varchar"; # default entity type |
|
330
|
|
|
|
|
|
|
bless { |
|
331
|
|
|
|
|
|
|
name => $virtual_table, # the virtual pivot table name |
|
332
|
|
|
|
|
|
|
table => $base_table, # the base table name |
|
333
|
|
|
|
|
|
|
columns => \@columns, # the base table distinct(pivot_column) values |
|
334
|
|
|
|
|
|
|
vcolumns => \@vcolumns, # the names of the virtual pivot table columns |
|
335
|
|
|
|
|
|
|
pivot_row => $pivot_row, # the name of the "pivot row" column in the base table |
|
336
|
|
|
|
|
|
|
pivot_row_type => $pivot_row_type, # the column affinity for the pivot row |
|
337
|
|
|
|
|
|
|
pivot_row_ref => $pivot_row_ref, # hash (see _parse_refspec) |
|
338
|
|
|
|
|
|
|
pivot_column => $pivot_column, # the name of the "pivot column" column in the base table |
|
339
|
|
|
|
|
|
|
pivot_column_ref => $pivot_column_ref, # hash (see _parse_refspec) |
|
340
|
|
|
|
|
|
|
pivot_value => $pivot_value, # the name of the "pivot value" column in the base table |
|
341
|
|
|
|
|
|
|
pivot_value_ref => $pivot_value_ref, # hash (see _parse_refspec) |
|
342
|
|
|
|
|
|
|
}, $class; |
|
343
|
|
|
|
|
|
|
} |
|
344
|
|
|
|
|
|
|
*CONNECT = \&CREATE; |
|
345
|
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
=over |
|
347
|
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
=item DECLARE_SQL |
|
349
|
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
Arguments: none |
|
351
|
|
|
|
|
|
|
Returns: a CREATE TABLE statement that specifies the columns of |
|
352
|
|
|
|
|
|
|
the virtual table. |
|
353
|
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
=cut |
|
355
|
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
sub DECLARE_SQL { |
|
357
|
|
|
|
|
|
|
trace "DECLARE_SQL"; |
|
358
|
|
|
|
|
|
|
my $self = shift; |
|
359
|
|
|
|
|
|
|
return sprintf "CREATE TABLE %s (%s)", $self->table, join ',', $self->vcolumns; |
|
360
|
|
|
|
|
|
|
} |
|
361
|
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
# Map from incoming operators to sql operators |
|
363
|
|
|
|
|
|
|
our %OpMap = ( 'eq' => '=', 'lt' => '<', 'gt' => '>', |
|
364
|
|
|
|
|
|
|
'ge' => '>=', 'le' => '<=', 'match' => 'like',); |
|
365
|
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
# Create a new temporary table and return its name. |
|
367
|
|
|
|
|
|
|
sub _new_temp_table { |
|
368
|
|
|
|
|
|
|
my ($count) = $db->select('sqlite_temp_master','count(1)')->list; |
|
369
|
|
|
|
|
|
|
debug "made temp table number ".($count + 1 ); |
|
370
|
|
|
|
|
|
|
return sprintf("temp_%d_%d",$count + 1,$$); |
|
371
|
|
|
|
|
|
|
} |
|
372
|
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
# Generate and run a query using information created during BEST_INDEX |
|
374
|
|
|
|
|
|
|
# calls. This is called during a FILTER call. |
|
375
|
|
|
|
|
|
|
# |
|
376
|
|
|
|
|
|
|
# Arguments : |
|
377
|
|
|
|
|
|
|
# cursor : an SQLite::VirtualTable::Pivot::Cursor object |
|
378
|
|
|
|
|
|
|
# constraints : an array ref of hashrefs whose keys are : |
|
379
|
|
|
|
|
|
|
# column_name - the name of the column |
|
380
|
|
|
|
|
|
|
# operator - one of the keys of %OpMap above |
|
381
|
|
|
|
|
|
|
# bind : an arrayref of bind values, one per constraint. |
|
382
|
|
|
|
|
|
|
# |
|
383
|
|
|
|
|
|
|
sub _do_query { |
|
384
|
|
|
|
|
|
|
my ($self, $cursor, $constraints, $args) = @_; |
|
385
|
|
|
|
|
|
|
my @values = @$args; # bind values for constraints |
|
386
|
|
|
|
|
|
|
my $ref = $self->pivot_value_ref; |
|
387
|
|
|
|
|
|
|
# Set up join clauses and table in case the value is a foreign key. |
|
388
|
|
|
|
|
|
|
my $join_clause = sprintf( |
|
389
|
|
|
|
|
|
|
" INNER JOIN %s ON %s.%s=%s.%s ", |
|
390
|
|
|
|
|
|
|
#e.g. " INNER JOIN value_s ON value_s.id=eav.value "; |
|
391
|
|
|
|
|
|
|
$ref->{table}, $ref->{table}, $ref->{child_key}, |
|
392
|
|
|
|
|
|
|
$self->table, $ref->{child_label} |
|
393
|
|
|
|
|
|
|
) if $self->pivot_row_ref; |
|
394
|
|
|
|
|
|
|
my $value_table = $ref->{table} || $self->table; |
|
395
|
|
|
|
|
|
|
my $value_column = $ref->{child_label} || $self->pivot_column; |
|
396
|
|
|
|
|
|
|
for my $constraint (@$constraints) { |
|
397
|
|
|
|
|
|
|
my $value = shift @values; |
|
398
|
|
|
|
|
|
|
my $temp_table = _new_temp_table(); |
|
399
|
|
|
|
|
|
|
push @{ $cursor->temp_tables }, $temp_table; |
|
400
|
|
|
|
|
|
|
debug "creating temporary table $temp_table "; |
|
401
|
|
|
|
|
|
|
my $key = $self->pivot_row_type =~ /int/i ? " INTEGER PRIMARY KEY" : ""; |
|
402
|
|
|
|
|
|
|
$db->query( sprintf("CREATE TEMPORARY TABLE %s (%s $key)", |
|
403
|
|
|
|
|
|
|
$temp_table, $self->pivot_row) |
|
404
|
|
|
|
|
|
|
) or die $db->error; |
|
405
|
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
my ($query,@bind); |
|
407
|
|
|
|
|
|
|
if ($constraint->{column_name} eq $self->pivot_row) { |
|
408
|
|
|
|
|
|
|
$query = sprintf( "INSERT INTO %s SELECT DISTINCT(%s) FROM %s WHERE %s %s ?", |
|
409
|
|
|
|
|
|
|
$temp_table, $self->pivot_row, $self->table, $self->pivot_row, $OpMap{$constraint->{operator}} ); |
|
410
|
|
|
|
|
|
|
@bind = ($value); |
|
411
|
|
|
|
|
|
|
} else { |
|
412
|
|
|
|
|
|
|
$query = sprintf( "INSERT INTO %s SELECT %s FROM %s %s WHERE %s = ? AND %s.%s %s ?", |
|
413
|
|
|
|
|
|
|
$temp_table, |
|
414
|
|
|
|
|
|
|
$self->pivot_row, |
|
415
|
|
|
|
|
|
|
$self->table, $join_clause, |
|
416
|
|
|
|
|
|
|
$self->pivot_column, |
|
417
|
|
|
|
|
|
|
$value_table, $self->pivot_value, $OpMap{$constraint->{operator}}); |
|
418
|
|
|
|
|
|
|
@bind = ( $constraint->{column_name}, $value); |
|
419
|
|
|
|
|
|
|
} |
|
420
|
|
|
|
|
|
|
debug "ready to run $query with @bind"; |
|
421
|
|
|
|
|
|
|
$db->query($query, @bind ) or die $db->error; |
|
422
|
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
debug ("temp table $temp_table is for $constraint->{column_name} $constraint->{operator} $value"); |
|
424
|
|
|
|
|
|
|
#info ("temp table $temp_table has : ".join ",", $db->select($temp_table,"*")->list); |
|
425
|
|
|
|
|
|
|
} |
|
426
|
|
|
|
|
|
|
debug "created ".scalar @{ $cursor->temp_tables }." temp table(s)"; |
|
427
|
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
# Now we have created the temp tables, join them together to make the final query. |
|
429
|
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
my $value_table_or_a = $self->pivot_value_ref ? $self->pivot_value_ref->{table} : 'a'; |
|
431
|
|
|
|
|
|
|
my $sql = sprintf( "SELECT a.%s, %s, %s.%s AS %s FROM %s a", |
|
432
|
|
|
|
|
|
|
$self->pivot_row, # == entity |
|
433
|
|
|
|
|
|
|
$self->pivot_column, # == attribute |
|
434
|
|
|
|
|
|
|
$value_table_or_a, |
|
435
|
|
|
|
|
|
|
( $self->pivot_value_ref |
|
436
|
|
|
|
|
|
|
? $self->pivot_value_ref->{child_label} |
|
437
|
|
|
|
|
|
|
: $self->pivot_value ), |
|
438
|
|
|
|
|
|
|
$self->pivot_value, |
|
439
|
|
|
|
|
|
|
$self->table); |
|
440
|
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
$sql .= sprintf(" INNER JOIN %s ON a.%s = %s.id ", |
|
442
|
|
|
|
|
|
|
$value_table_or_a, $self->pivot_value, $value_table_or_a ) if $self->pivot_value_ref; |
|
443
|
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
for my $temp_table ($cursor->temp_tables) { |
|
445
|
|
|
|
|
|
|
$sql .= sprintf( " INNER JOIN %s ON %s.%s=a.%s ", |
|
446
|
|
|
|
|
|
|
$temp_table, $temp_table, |
|
447
|
|
|
|
|
|
|
$self->pivot_row, $self->pivot_row |
|
448
|
|
|
|
|
|
|
); |
|
449
|
|
|
|
|
|
|
} |
|
450
|
|
|
|
|
|
|
$sql .= sprintf(" ORDER BY a.%s", $self->pivot_row); |
|
451
|
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
# TODO move into cursor.pm |
|
453
|
|
|
|
|
|
|
my (@current_row); |
|
454
|
|
|
|
|
|
|
$cursor->reset; |
|
455
|
|
|
|
|
|
|
$cursor->{sth} = $db->dbh->prepare( $sql) or die "error in $sql : $DBI::errstr"; |
|
456
|
|
|
|
|
|
|
$cursor->sth->execute or die $DBI::errstr; |
|
457
|
|
|
|
|
|
|
$cursor->set( "last" => !( @current_row = $cursor->sth->fetchrow_array ) ); |
|
458
|
|
|
|
|
|
|
$cursor->set( current_row => \@current_row ); |
|
459
|
|
|
|
|
|
|
debug "ran query, first row is : @current_row"; |
|
460
|
|
|
|
|
|
|
} |
|
461
|
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
=item OPEN |
|
463
|
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
Create and return a new cursor. |
|
465
|
|
|
|
|
|
|
This returns a new SQLite::VirtualTable::Pivot::Cursor object. |
|
466
|
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
This is called before BEST_INDEX or FILTER, just to create the |
|
468
|
|
|
|
|
|
|
new empty object. |
|
469
|
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
=cut |
|
471
|
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
sub OPEN { |
|
473
|
|
|
|
|
|
|
my $self = shift; |
|
474
|
|
|
|
|
|
|
trace "(OPEN $self->{name})"; |
|
475
|
|
|
|
|
|
|
return SQLite::VirtualTable::Pivot::Cursor->new({virtual_table => $self})->reset; |
|
476
|
|
|
|
|
|
|
} |
|
477
|
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
=item BEST_INDEX |
|
479
|
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
Given a set of constraints and an order, return the name |
|
481
|
|
|
|
|
|
|
(and number) of the best index that should be used to |
|
482
|
|
|
|
|
|
|
run this query, and the cost of using this index. |
|
483
|
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
See SQLite::VirtualTable for a more complete description of |
|
485
|
|
|
|
|
|
|
the incoming and outgoing parameters. |
|
486
|
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
=cut |
|
488
|
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
sub BEST_INDEX { |
|
490
|
|
|
|
|
|
|
my ($self,$constraints,$order_bys) = @_; |
|
491
|
|
|
|
|
|
|
trace "(BEST_INDEX)"; |
|
492
|
|
|
|
|
|
|
# $order_bys is an arrayref of hashrefs with keys "column" and "direction". |
|
493
|
|
|
|
|
|
|
$self->{indexes} ||= []; |
|
494
|
|
|
|
|
|
|
$self->{counts} ||= {}; |
|
495
|
|
|
|
|
|
|
my $index_number = @{ $self->indexes }; |
|
496
|
|
|
|
|
|
|
my $index_name = "index_".$index_number; |
|
497
|
|
|
|
|
|
|
( $self->counts->{__table__} ) = $db->select( $self->table, 'count(1)', )->list; |
|
498
|
|
|
|
|
|
|
my $cost = $self->counts->{__table__}; |
|
499
|
|
|
|
|
|
|
my $i = 0; |
|
500
|
|
|
|
|
|
|
my @index_constraints; |
|
501
|
|
|
|
|
|
|
# We are going to build an "index" (in name only) for this set of |
|
502
|
|
|
|
|
|
|
# constraints. The cost will be the total number of matching attributes |
|
503
|
|
|
|
|
|
|
# in the table for each of the constraints. |
|
504
|
|
|
|
|
|
|
my %seen_column; |
|
505
|
|
|
|
|
|
|
for my $constraint (@$constraints) { |
|
506
|
|
|
|
|
|
|
# Keys of $constraint are : operator, usable, column. |
|
507
|
|
|
|
|
|
|
# We must fill in : arg_index, omit. |
|
508
|
|
|
|
|
|
|
next unless $constraint->{usable}; |
|
509
|
|
|
|
|
|
|
$cost ||= 0; |
|
510
|
|
|
|
|
|
|
my $column_name = $self->{columns}[$constraint->{column}]; |
|
511
|
|
|
|
|
|
|
debug "evaluating cost of using column $column_name, operator $constraint->{operator}"; |
|
512
|
|
|
|
|
|
|
$constraint->{arg_index} = $i++; # index of this constraint as it comes through in @args to FILTER |
|
513
|
|
|
|
|
|
|
$constraint->{omit} = 1; |
|
514
|
|
|
|
|
|
|
push @index_constraints, { |
|
515
|
|
|
|
|
|
|
operator => $constraint->{operator}, |
|
516
|
|
|
|
|
|
|
column_name => $column_name |
|
517
|
|
|
|
|
|
|
}; |
|
518
|
|
|
|
|
|
|
unless (defined($self->counts->{$column_name})) { |
|
519
|
|
|
|
|
|
|
# TODO cache these (when creating the table?) |
|
520
|
|
|
|
|
|
|
( $self->counts->{$column_name} ) = |
|
521
|
|
|
|
|
|
|
$db->select( $self->table, 'count(1)', |
|
522
|
|
|
|
|
|
|
{ $self->pivot_column => $column_name } )->list; |
|
523
|
|
|
|
|
|
|
} |
|
524
|
|
|
|
|
|
|
my $this_cost = $self->counts->{$column_name}; |
|
525
|
|
|
|
|
|
|
#debug "this cost is $this_cost"; |
|
526
|
|
|
|
|
|
|
$cost -= $this_cost unless $seen_column{$column_name}++; |
|
527
|
|
|
|
|
|
|
} |
|
528
|
|
|
|
|
|
|
push @{ $self->indexes }, { constraints => \@index_constraints, name => $index_name, cost => $cost }; |
|
529
|
|
|
|
|
|
|
unless (defined($cost)) { |
|
530
|
|
|
|
|
|
|
($cost) = $db->select($self->{table},'count(1)')->flat; |
|
531
|
|
|
|
|
|
|
debug "cost is num of rows which is $cost"; |
|
532
|
|
|
|
|
|
|
} |
|
533
|
|
|
|
|
|
|
my $order_by_consumed = 0; |
|
534
|
|
|
|
|
|
|
if ( @$order_bys == 1 ) |
|
535
|
|
|
|
|
|
|
{ # only consumed if we are ordering by the pivot_row in ascending order |
|
536
|
|
|
|
|
|
|
if ( $self->columns->[ $order_bys->[0]{column} ] eq $self->pivot_row |
|
537
|
|
|
|
|
|
|
&& $order_bys->[0]{direction} == 1 ) { |
|
538
|
|
|
|
|
|
|
$order_by_consumed = 1; |
|
539
|
|
|
|
|
|
|
} |
|
540
|
|
|
|
|
|
|
} |
|
541
|
|
|
|
|
|
|
debug "returning: index $index_number ($index_name) has cost $cost (orderconsumed: $order_by_consumed)"; |
|
542
|
|
|
|
|
|
|
return ( $index_number, $index_name, $order_by_consumed, $cost ); |
|
543
|
|
|
|
|
|
|
} |
|
544
|
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
=item FILTER |
|
546
|
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
Given a cursor and an index number (created dynamically in BEST_FILTER) |
|
548
|
|
|
|
|
|
|
and the @args to pass to the index, run the query on the base table, |
|
549
|
|
|
|
|
|
|
joining as necessary to filter the results. |
|
550
|
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
=cut |
|
552
|
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
sub FILTER { |
|
554
|
|
|
|
|
|
|
# called after OPEN, before NEXT |
|
555
|
|
|
|
|
|
|
my ($self, $cursor, $idxnum, $idxstr, @args) = @_; |
|
556
|
|
|
|
|
|
|
trace "(FILTER $cursor)"; |
|
557
|
|
|
|
|
|
|
debug "filter -- index chosen was $idxnum ($idxstr) "; |
|
558
|
|
|
|
|
|
|
my $constraints = $self->indexes->[$idxnum]{constraints}; |
|
559
|
|
|
|
|
|
|
debug "FILTER Is calling _do_query for $cursor"; |
|
560
|
|
|
|
|
|
|
$cursor->reset; |
|
561
|
|
|
|
|
|
|
$self->_do_query( $cursor, $constraints, \@args ); |
|
562
|
|
|
|
|
|
|
$self->NEXT($cursor); |
|
563
|
|
|
|
|
|
|
} |
|
564
|
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
=item EOF |
|
566
|
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
Are there any more rows left? |
|
568
|
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
=cut |
|
570
|
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
sub EOF { |
|
572
|
|
|
|
|
|
|
my ($self, $cursor ) = @_; |
|
573
|
|
|
|
|
|
|
$cursor->done; |
|
574
|
|
|
|
|
|
|
}; |
|
575
|
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
sub _row_values_are_equal { |
|
577
|
|
|
|
|
|
|
my $self = shift; |
|
578
|
|
|
|
|
|
|
my ($val1,$val2) = @_; |
|
579
|
|
|
|
|
|
|
return $val1==$val2 if $self->pivot_row_type =~ /integer/i; |
|
580
|
|
|
|
|
|
|
return $val1 eq $val2; |
|
581
|
|
|
|
|
|
|
} |
|
582
|
|
|
|
|
|
|
|
|
583
|
|
|
|
|
|
|
=item NEXT |
|
584
|
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
Advance the cursor one row. |
|
586
|
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
=cut |
|
588
|
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
sub NEXT { |
|
590
|
|
|
|
|
|
|
my ($self,$cursor) = @_; |
|
591
|
|
|
|
|
|
|
trace "(NEXT $cursor)"; |
|
592
|
|
|
|
|
|
|
$cursor->get_next_row; |
|
593
|
|
|
|
|
|
|
} |
|
594
|
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
=item COLUMN |
|
596
|
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
Get a piece of data from a given column (and the current row). |
|
598
|
|
|
|
|
|
|
|
|
599
|
|
|
|
|
|
|
=cut |
|
600
|
|
|
|
|
|
|
|
|
601
|
|
|
|
|
|
|
sub COLUMN { |
|
602
|
|
|
|
|
|
|
my ($self, $cursor, $n) = @_; |
|
603
|
|
|
|
|
|
|
my $value = $cursor->column_value( $self->{columns}[$n] ); |
|
604
|
|
|
|
|
|
|
return looks_like_number($value) ? 0 + $value : $value; |
|
605
|
|
|
|
|
|
|
} |
|
606
|
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
=item ROWID |
|
608
|
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
Generate a unique id for this row. |
|
610
|
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
=cut |
|
612
|
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
sub ROWID { |
|
614
|
|
|
|
|
|
|
my ($self, $cursor) = @_; |
|
615
|
|
|
|
|
|
|
return $cursor->row_id; |
|
616
|
|
|
|
|
|
|
} |
|
617
|
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
=item CLOSE |
|
619
|
|
|
|
|
|
|
|
|
620
|
|
|
|
|
|
|
Close the cursor. |
|
621
|
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
=cut |
|
623
|
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
sub CLOSE { |
|
625
|
|
|
|
|
|
|
my ($self,$cursor) = @_; |
|
626
|
|
|
|
|
|
|
trace "(CLOSE $cursor)"; |
|
627
|
|
|
|
|
|
|
for ($cursor->temp_tables) { |
|
628
|
|
|
|
|
|
|
$db->query("drop table $_") or warn "error dropping $_: ".$db->error; |
|
629
|
|
|
|
|
|
|
} |
|
630
|
|
|
|
|
|
|
} |
|
631
|
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
=item DROP |
|
633
|
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
Drop the virtual table. |
|
635
|
|
|
|
|
|
|
|
|
636
|
|
|
|
|
|
|
=cut |
|
637
|
|
|
|
|
|
|
|
|
638
|
|
|
|
|
|
|
sub DROP { |
|
639
|
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
} |
|
641
|
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
=item DISCONNECT |
|
643
|
|
|
|
|
|
|
|
|
644
|
|
|
|
|
|
|
Disconnect from the database. |
|
645
|
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
=cut |
|
647
|
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
sub DISCONNECT {} |
|
649
|
|
|
|
|
|
|
|
|
650
|
|
|
|
|
|
|
*DESTROY = \&DISCONNECT; |
|
651
|
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
=back |
|
653
|
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
=head1 TODO |
|
655
|
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
- re-use the existing database handle (requires changes |
|
657
|
|
|
|
|
|
|
to SQLite::VirtualTable and DBD::SQLite) |
|
658
|
|
|
|
|
|
|
- allow modification of the data in the virtual table |
|
659
|
|
|
|
|
|
|
- allow value column to not have integer affinity |
|
660
|
|
|
|
|
|
|
- more optimization |
|
661
|
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
663
|
|
|
|
|
|
|
|
|
664
|
|
|
|
|
|
|
L |
|
665
|
|
|
|
|
|
|
|
|
666
|
|
|
|
|
|
|
L |
|
667
|
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
=cut |
|
669
|
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
1; |
|
671
|
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
|