|  line  | 
 stmt  | 
 bran  | 
 cond  | 
 sub  | 
 pod  | 
 time  | 
 code  | 
| 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 package SQL::Abstract::Prefetch;  | 
| 
2
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
3
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 our $VERSION = '0.001';  | 
| 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 NAME  | 
| 
6
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
7
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 SQL::Abstract::Prefetch - implement "prefetch" for DBI RDBMS  | 
| 
8
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
9
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 SYNOPSIS  | 
| 
10
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
11
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   my $queryspec = {  | 
| 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     table => 'blog',  | 
| 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     fields => [  | 
| 
14
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       'html',  | 
| 
15
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       'id',  | 
| 
16
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       'is_published',  | 
| 
17
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       'markdown',  | 
| 
18
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       'slug',  | 
| 
19
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       'title',  | 
| 
20
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       'user_id',  | 
| 
21
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     ],  | 
| 
22
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     keys => [ 'id' ],  | 
| 
23
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     multi => {  | 
| 
24
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       comments => {  | 
| 
25
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         table => 'comment',  | 
| 
26
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         fields => [ 'blog_id', 'html', 'id', 'markdown', 'user_id' ],  | 
| 
27
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         keys => [ 'id' ],  | 
| 
28
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       },  | 
| 
29
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     },  | 
| 
30
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     single => {  | 
| 
31
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       user => {  | 
| 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         table => 'user',  | 
| 
33
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         fields => [ 'access', 'age', 'email', 'id', 'password', 'username' ],  | 
| 
34
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         keys => [ 'id' ],  | 
| 
35
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       },  | 
| 
36
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     },  | 
| 
37
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   };  | 
| 
38
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   my $abstract = SQL::Abstract::Pg->new( name_sep => '.', quote_char => '"' );  | 
| 
39
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   my $dbh = DBI->connect( "dbi:SQLite:dbname=filename.db", '', '' );  | 
| 
40
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   my $prefetch = SQL::Abstract::Prefetch->new(  | 
| 
41
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     abstract => $abstract,  | 
| 
42
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     dbhgetter => sub { $dbh },  | 
| 
43
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     dbcatalog => undef, # for SQLite  | 
| 
44
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     dbschema => undef,  | 
| 
45
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     filter_table => sub { $_[0] !~ /^sqlite_/ },  | 
| 
46
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   );  | 
| 
47
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   my ( $sql, @bind ) = $prefetch->select_from_queryspec(  | 
| 
48
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     $queryspec,  | 
| 
49
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     { id => $items{blog}[0]{id} },  | 
| 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   );  | 
| 
51
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   my ( $extractspec ) = $prefetch->extractspec_from_queryspec( $queryspec );  | 
| 
52
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   my $sth = $dbh->prepare( $sql );  | 
| 
53
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   $sth->execute( @bind );  | 
| 
54
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   my ( $got ) = $prefetch->extract_from_query( $extractspec, $sth );  | 
| 
55
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
56
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 DESCRIPTION  | 
| 
57
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
58
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 This class implements "prefetch" in the style of L. Stages  | 
| 
59
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 of operation:  | 
| 
60
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
61
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =over  | 
| 
62
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
63
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =item *  | 
| 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
65
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Generate a "query spec" that describes what you want back from the  | 
| 
66
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 database - which fields from which tables, and what relations to join.  | 
| 
67
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
68
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =item *  | 
| 
69
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
70
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Generate SQL (and bind parameters) from that "query spec".  | 
| 
71
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
72
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =item *  | 
| 
73
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
74
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Pass the SQL and parameters to a L C<$dbh> to prepare and execute.  | 
| 
75
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
76
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =item *  | 
| 
77
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
78
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Pass the C<$sth> when ready (this allows for asynchronous operation)  | 
| 
79
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 to the extractor method to turn the returned rows into the hash-refs  | 
| 
80
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 represented, including array-ref values for any "has many" relationships.  | 
| 
81
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
82
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =back  | 
| 
83
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
84
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 ATTRIBUTES  | 
| 
85
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
86
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head2 abstract  | 
| 
87
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
88
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Currently, must be a L object.  | 
| 
89
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
90
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head2 dbhgetter  | 
| 
91
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
92
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 A code-ref that returns a L C<$dbh>.  | 
| 
93
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
94
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head2 dbcatalog  | 
| 
95
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
96
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 The L "catalog" argument for e.g. L.  | 
| 
97
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
98
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head2 dbschema  | 
| 
99
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
100
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 The L "schema" argument for e.g. L.  | 
| 
101
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
102
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head2 filter_table  | 
| 
103
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
104
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Coderef called with a table name, returns a boolean of true to keep, false  | 
| 
105
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 to discard - typically for a system table.  | 
| 
106
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
107
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head2 multi_namer  | 
| 
108
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
109
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Coderef called with a table name, returns a suitable name for the relation  | 
| 
110
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 to that table. Defaults to L.  | 
| 
111
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
112
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head2 dbspec  | 
| 
113
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
114
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 By default, will be calculated from the supplied C<$dbh>, using the  | 
| 
115
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 supplied C, C, C, C,  | 
| 
116
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 and C. May however be supplied, in which case those other  | 
| 
117
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 attributes are not needed.  | 
| 
118
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
119
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 A "database spec"; a hash-ref mapping tables to maps of the  | 
| 
120
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 relation-name (a string) to a further hash-ref with keys:  | 
| 
121
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
122
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =over  | 
| 
123
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
124
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =item type  | 
| 
125
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
126
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 either C or C  | 
| 
127
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
128
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =item fromkey  | 
| 
129
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
130
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 the column name in the "from" table  | 
| 
131
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
132
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =item fromtable  | 
| 
133
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
134
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 the name of the "from" table  | 
| 
135
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
136
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =item tokey  | 
| 
137
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
138
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 the column name in the "to" table  | 
| 
139
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
140
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =item totable  | 
| 
141
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
142
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 the name of the "to" table  | 
| 
143
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
144
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =back  | 
| 
145
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
146
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 The relation-name for "multi" will be calculated using  | 
| 
147
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 the C on the remote table name.  | 
| 
148
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
149
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 METHODS  | 
| 
150
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
151
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head2 select_from_queryspec  | 
| 
152
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
153
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Parameters:  | 
| 
154
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
155
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =over  | 
| 
156
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
157
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =item *  | 
| 
158
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
159
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 a "query spec"; a hash-ref with these keys:  | 
| 
160
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
161
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =over  | 
| 
162
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
163
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =item table  | 
| 
164
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
165
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =item keys  | 
| 
166
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
167
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 array-ref of fields that are primary keys on this table  | 
| 
168
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
169
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =item fields  | 
| 
170
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
171
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 array-ref of fields that are primitive types to show in result,  | 
| 
172
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 including PKs if wanted. If not wanted, the joins still function.  | 
| 
173
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
174
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =item single  | 
| 
175
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
176
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 hash-ref mapping relation-names to "query specs" - a recursive data  | 
| 
177
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 structure; the relation is "has one"  | 
| 
178
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
179
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =item multi  | 
| 
180
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
181
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 hash-ref mapping relation-names to "relate specs" as above; the relation is  | 
| 
182
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 "has many"  | 
| 
183
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
184
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =back  | 
| 
185
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
186
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =item *  | 
| 
187
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
188
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 an L "where" specification  | 
| 
189
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
190
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =item *  | 
| 
191
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
192
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 an L "options" specification, including C,  | 
| 
193
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 C, and C  | 
| 
194
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
195
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =back  | 
| 
196
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
197
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Returns the generated SQL, then a list of parameters to bind.  | 
| 
198
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
199
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head2 extractspec_from_queryspec  | 
| 
200
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
201
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Parameters: a "query spec" as above.  | 
| 
202
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
203
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Returns an opaque "extract spec": data to be used by  | 
| 
204
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 L to interpret results generated from the  | 
| 
205
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 L query.  | 
| 
206
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
207
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head2 extract_from_query  | 
| 
208
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
209
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Parameters: an opaque "extract spec" created by  | 
| 
210
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 L, and a L C<$sth>.  | 
| 
211
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
212
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Returns a list of hash-refs of items as reconstructed according to the spec.  | 
| 
213
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
214
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 SEE ALSO  | 
| 
215
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
216
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 L, L, L  | 
| 
217
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
218
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =cut  | 
| 
219
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
220
 | 
1
 | 
 
 | 
 
 | 
  
1
  
 | 
 
 | 
258618
 | 
 use Mojo::Base '-base';  | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
12
 | 
    | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
    | 
| 
221
 | 
1
 | 
 
 | 
 
 | 
  
1
  
 | 
 
 | 
717
 | 
 use Lingua::EN::Inflect::Number ();  | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
30268
 | 
    | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
33
 | 
    | 
| 
222
 | 
1
 | 
 
 | 
 
 | 
  
1
  
 | 
 
 | 
8
 | 
 use Scalar::Util qw( looks_like_number );  | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
3
 | 
    | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2531
 | 
    | 
| 
223
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
224
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 has 'abstract';  | 
| 
225
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 has 'dbhgetter';  | 
| 
226
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 has 'dbcatalog';  | 
| 
227
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 has 'dbschema';  | 
| 
228
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 has 'filter_table';  | 
| 
229
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 has multi_namer => sub { \&Lingua::EN::Inflect::Number::to_PL };  | 
| 
230
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 has dbspec => \&_build_dbspec;  | 
| 
231
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
232
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub select_from_queryspec {  | 
| 
233
 | 
5
 | 
 
 | 
 
 | 
  
5
  
 | 
  
1
  
 | 
28
 | 
   my ( $self, $queryspec, $where, $origopt ) = @_;  | 
| 
234
 | 
5
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
22
 | 
   my %opt = %{ $origopt || {} };  | 
| 
 
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
23
 | 
    | 
| 
235
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
19
 | 
   my ( $talias, $sources, $columns ) = $self->_sc_from_queryspec(  | 
| 
236
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     $queryspec,  | 
| 
237
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   );  | 
| 
238
 | 
5
 | 
 
 | 
  
 66
  
 | 
 
 | 
 
 | 
28
 | 
   $opt{order_by} = _order_by( $opt{order_by} || $queryspec->{keys}, $talias );  | 
| 
239
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
11
 | 
   my $limit = delete $opt{limit};  | 
| 
240
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
12
 | 
   my $offset = delete $opt{offset};  | 
| 
241
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
13
 | 
   my $abstract = $self->abstract;  | 
| 
242
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
33
 | 
   my %inner = %$queryspec;  | 
| 
243
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
15
 | 
   delete @inner{qw(single multi)};  | 
| 
244
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
14
 | 
   my ( undef, $inner_s, $inner_c ) = $self->_sc_from_queryspec( \%inner );  | 
| 
245
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
11
 | 
   my @inner_c2 = (@{ $queryspec->{keys} }, @{ $queryspec->{fields} });  | 
| 
 
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
12
 | 
    | 
| 
 
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
12
 | 
    | 
| 
246
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   # this is to dedup colnames as MySQL blows up if select same column > 1 time  | 
| 
247
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   # - at least in inner select - so use aliased ones for keys = already got  | 
| 
248
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
10
 | 
   my %keysmap = map {$_=>1} @{ $queryspec->{keys} };  | 
| 
 
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
15
 | 
    | 
| 
 
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
10
 | 
    | 
| 
249
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
11
 | 
   my $keyscount = @{ $queryspec->{keys} };  | 
| 
 
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
10
 | 
    | 
| 
250
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   $inner_c2[$_] = $inner_c->[$_]  | 
| 
251
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
10
 | 
     for grep $keysmap{$inner_c2[$_]}, $keyscount..$keyscount + $#{$queryspec->{fields}};  | 
| 
 
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
28
 | 
    | 
| 
252
 | 
5
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
35
 | 
   my ( $inner_sql, @bind ) = $abstract->select(  | 
| 
253
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     $inner_s,  | 
| 
254
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     \@inner_c2,  | 
| 
255
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     $where,  | 
| 
256
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     (keys %opt ? \%opt : undef),  | 
| 
257
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   );  | 
| 
258
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
4311
 | 
   $inner_sql .= _limit_offset( $limit, $offset );  | 
| 
259
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   return ( $inner_sql, @bind )  | 
| 
260
 | 
5
 | 
  
100
  
 | 
  
 66
  
 | 
 
 | 
 
 | 
8
 | 
     if !%{ $queryspec->{single} || {} } and !%{ $queryspec->{multi} || {} };  | 
| 
 
 | 
5
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
29
 | 
    | 
| 
 
 | 
3
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
46
 | 
    | 
| 
261
 | 
2
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
   $inner_sql = "( $inner_sql ) as $talias";  | 
| 
262
 | 
2
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
   $sources->[0] = \$inner_sql;  | 
| 
263
 | 
2
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
9
 | 
   my ( $sql ) = $abstract->select(  | 
| 
264
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     $sources,  | 
| 
265
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     $columns,  | 
| 
266
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     undef,  | 
| 
267
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     (keys %opt ? \%opt : undef),  | 
| 
268
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   );  | 
| 
269
 | 
2
 | 
 
 | 
 
 | 
 
 | 
 
 | 
4241
 | 
   ( $sql, @bind );  | 
| 
270
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
271
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
272
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub _limit_offset {  | 
| 
273
 | 
5
 | 
 
 | 
 
 | 
  
5
  
 | 
 
 | 
11
 | 
   my ( $limit, $offset ) = @_;  | 
| 
274
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
   my $extra = '';  | 
| 
275
 | 
5
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
13
 | 
   if ( $limit ) {  | 
| 
276
 | 
1
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
6
 | 
     die "Limit must be number" if !looks_like_number $limit;  | 
| 
277
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
4
 | 
     $extra .= ' LIMIT ' . $limit;  | 
| 
278
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   }  | 
| 
279
 | 
5
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
13
 | 
   if ( $offset ) {  | 
| 
280
 | 
1
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
28
 | 
     die "Offset must be number" if !looks_like_number $offset;  | 
| 
281
 | 
1
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
4
 | 
     $extra .= ' LIMIT ' . 2**32 if !$limit;  | 
| 
282
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
4
 | 
     $extra .= ' OFFSET ' . $offset;  | 
| 
283
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   }  | 
| 
284
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
15
 | 
   $extra;  | 
| 
285
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
286
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
287
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub _order_by {  | 
| 
288
 | 
8
 | 
 
 | 
 
 | 
  
8
  
 | 
 
 | 
16
 | 
   my ( $order, $talias ) = @_;  | 
| 
289
 | 
8
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
17
 | 
   return undef if !$order;  | 
| 
290
 | 
8
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
27
 | 
   if ( ref $order eq 'ARRAY' ) {  | 
| 
 
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
291
 | 
3
 | 
 
 | 
 
 | 
 
 | 
 
 | 
11
 | 
     return [ map _order_by( $_, $talias ), @$order ];  | 
| 
292
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   } elsif ( ref $order eq 'HASH' ) {  | 
| 
293
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
5
 | 
     my @o_b = %$order;  | 
| 
294
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
5
 | 
     return { $o_b[0] => "$talias.$o_b[1]" };  | 
| 
295
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   } else {  | 
| 
296
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
19
 | 
     return "$talias.$order";  | 
| 
297
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   }  | 
| 
298
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
299
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
300
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # SQLA sources, columns  | 
| 
301
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub _sc_from_queryspec {  | 
| 
302
 | 
14
 | 
 
 | 
 
 | 
  
14
  
 | 
 
 | 
31
 | 
   my ( $self, $queryspec, $calias, $talias ) = @_;  | 
| 
303
 | 
14
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
51
 | 
   $calias //= 'c000';  | 
| 
304
 | 
14
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
47
 | 
   $talias //= 't000';  | 
| 
305
 | 
14
 | 
 
 | 
 
 | 
 
 | 
 
 | 
26
 | 
   my $my_talias = ++$talias;  | 
| 
306
 | 
14
 | 
 
 | 
 
 | 
 
 | 
 
 | 
25
 | 
   my $coll = $queryspec->{table};  | 
| 
307
 | 
14
 | 
 
 | 
 
 | 
 
 | 
 
 | 
38
 | 
   my $dbspec = $self->dbspec;  | 
| 
308
 | 
14
 | 
 
 | 
 
 | 
 
 | 
 
 | 
75
 | 
   my $abstract = $self->abstract;  | 
| 
309
 | 
14
 | 
 
 | 
 
 | 
 
 | 
 
 | 
60
 | 
   my $sep = $abstract->{name_sep};  | 
| 
310
 | 
14
 | 
 
 | 
 
 | 
 
 | 
 
 | 
43
 | 
   my @sources = ( \( $abstract->_quote( $coll ) . ' as ' . $my_talias ) );  | 
| 
311
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   my @columns = map [ qq{$my_talias.$_}, ++$calias ],  | 
| 
312
 | 
14
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
47
 | 
     @{ $queryspec->{keys} || [] },  | 
| 
313
 | 
14
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
339
 | 
     @{ $queryspec->{fields} || [] };  | 
| 
 
 | 
14
 | 
 
 | 
 
 | 
 
 | 
 
 | 
144
 | 
    | 
| 
314
 | 
14
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
58
 | 
   my $single = $queryspec->{single} || {};  | 
| 
315
 | 
14
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
39
 | 
   my $multi = $queryspec->{multi} || {};  | 
| 
316
 | 
14
 | 
 
 | 
 
 | 
 
 | 
 
 | 
42
 | 
   my %allrelations = ( %$single, %$multi );  | 
| 
317
 | 
14
 | 
 
 | 
 
 | 
 
 | 
 
 | 
36
 | 
   for my $relname ( sort( keys %$single ), sort( keys %$multi ) ) {  | 
| 
318
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
     my $relation = $allrelations{ $relname };  | 
| 
319
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
     my $other_coll = $relation->{table};  | 
| 
320
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 #use Test::More; diag "fkinfo all($coll=$my_talias) ", explain $dbspec->{ $coll };  | 
| 
321
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
     my $fkinfo = $dbspec->{ $coll }{ $relname };  | 
| 
322
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 #use Test::More; diag 'fkinfo ', explain $fkinfo;  | 
| 
323
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
15
 | 
     ( my $to_talias, my $other_s, my $other_c, $calias, $talias ) =  | 
| 
324
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       $self->_sc_from_queryspec( $relation, $calias, $talias );  | 
| 
325
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     my $totable =  | 
| 
326
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
15
 | 
       \( $abstract->_quote( $fkinfo->{totable} ) . ' as ' . $to_talias );  | 
| 
327
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
81
 | 
     my $tokey = $to_talias . $sep . $fkinfo->{tokey};  | 
| 
328
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
     my $fromkey = $my_talias . $sep . $fkinfo->{fromkey};  | 
| 
329
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
10
 | 
     $other_s->[0] = [ -left => $totable, $tokey, $fromkey ];  | 
| 
330
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
     push @sources, @$other_s;  | 
| 
331
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
15
 | 
     push @columns, @$other_c;  | 
| 
332
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   }  | 
| 
333
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 #use Test::More; diag 'sfr so far ', explain [ \@sources, \@columns ];  | 
| 
334
 | 
14
 | 
 
 | 
 
 | 
 
 | 
 
 | 
71
 | 
   ( $my_talias, \@sources, \@columns, $calias, $talias );  | 
| 
335
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
336
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
337
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # each "strip" = hashref:  | 
| 
338
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 #  keys=start,finish  | 
| 
339
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 #  fields=start,finish  | 
| 
340
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 #  fieldnames  | 
| 
341
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 #  offset  | 
| 
342
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 #  type (single=0, multi=1)  | 
| 
343
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 #  specsindex  | 
| 
344
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 #  subspecs (arrayref of pairs: [key, spec])  | 
| 
345
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub extractspec_from_queryspec {  | 
| 
346
 | 
9
 | 
 
 | 
 
 | 
  
9
  
 | 
  
1
  
 | 
32593
 | 
   my ( $self, $queryspec, $offset, $type, $myspecsindex ) = @_;  | 
| 
347
 | 
9
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
38
 | 
   $myspecsindex //= 0;  | 
| 
348
 | 
9
 | 
 
 | 
 
 | 
 
 | 
 
 | 
16
 | 
   my $specsindex = $myspecsindex;  | 
| 
349
 | 
9
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
25
 | 
   $offset //= 0;  | 
| 
350
 | 
9
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
27
 | 
   $type //= 1; # default = top-level, which is a special-case multi  | 
| 
351
 | 
9
 | 
 
 | 
 
 | 
 
 | 
 
 | 
13
 | 
   my $keyscount = @{ $queryspec->{keys} };  | 
| 
 
 | 
9
 | 
 
 | 
 
 | 
 
 | 
 
 | 
17
 | 
    | 
| 
352
 | 
9
 | 
 
 | 
 
 | 
 
 | 
 
 | 
14
 | 
   my @fields = @{ $queryspec->{fields} };  | 
| 
 
 | 
9
 | 
 
 | 
 
 | 
 
 | 
 
 | 
27
 | 
    | 
| 
353
 | 
9
 | 
 
 | 
 
 | 
 
 | 
 
 | 
18
 | 
   my $highcount = $keyscount + $#fields;  | 
| 
354
 | 
9
 | 
 
 | 
 
 | 
 
 | 
 
 | 
45
 | 
   my @specs = {  | 
| 
355
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     keys => [ 0, $keyscount - 1 ],  | 
| 
356
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     fields => [ $keyscount, $highcount ],  | 
| 
357
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     fieldnames => \@fields,  | 
| 
358
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     offset => $offset,  | 
| 
359
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     type => $type,  | 
| 
360
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     specsindex => $myspecsindex,  | 
| 
361
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   };  | 
| 
362
 | 
9
 | 
 
 | 
 
 | 
 
 | 
 
 | 
15
 | 
   my @subspecs;  | 
| 
363
 | 
9
 | 
 
 | 
 
 | 
 
 | 
 
 | 
12
 | 
   $offset += $highcount + 1;  | 
| 
364
 | 
9
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
32
 | 
   my $single = $queryspec->{single} || {};  | 
| 
365
 | 
9
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
28
 | 
   my $multi = $queryspec->{multi} || {};  | 
| 
366
 | 
9
 | 
 
 | 
 
 | 
 
 | 
 
 | 
39
 | 
   for (  | 
| 
367
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     ( map [ 0, $_, $single->{$_} ], sort keys %$single ),  | 
| 
368
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     ( map [ 1, $_, $multi->{$_} ], sort keys %$multi ),  | 
| 
369
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   ) {  | 
| 
370
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
16
 | 
     ( my $otherspecs, $offset, $specsindex ) = $self->extractspec_from_queryspec(  | 
| 
371
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       $_->[2],  | 
| 
372
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       $offset,  | 
| 
373
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       $_->[0], # single  | 
| 
374
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       ++$specsindex,  | 
| 
375
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     );  | 
| 
376
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
     push @specs, @$otherspecs;  | 
| 
377
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
11
 | 
     push @subspecs, [ $_->[1], $otherspecs->[0] ];  | 
| 
378
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   }  | 
| 
379
 | 
9
 | 
 
 | 
 
 | 
 
 | 
 
 | 
21
 | 
   $specs[0]->{subspecs} = \@subspecs;  | 
| 
380
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 #use Test::More; diag "esfr ", explain [ $queryspec, \@specs ];  | 
| 
381
 | 
9
 | 
 
 | 
 
 | 
 
 | 
 
 | 
32
 | 
   ( \@specs, $offset, $specsindex );  | 
| 
382
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
383
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
384
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub extract_from_query {  | 
| 
385
 | 
5
 | 
 
 | 
 
 | 
  
5
  
 | 
  
1
  
 | 
781
 | 
   my ( $self, $extractspec, $sth ) = @_;  | 
| 
386
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
11
 | 
   my @ret;  | 
| 
387
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   my @index2ids; # ids = array-ref of the PKs for this spec we are "on"  | 
| 
388
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   # entrypoint = ref to update if new; scalar for single, array for multi  | 
| 
389
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
12
 | 
   my @index2entrypoint = ( \@ret ); # zero-th is the overall return  | 
| 
390
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
58
 | 
   while ( my $array = $sth->fetchrow_arrayref ) {  | 
| 
391
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 #use Test::More; diag 'after select, not undef ', explain $array;  | 
| 
392
 | 
8
 | 
 
 | 
 
 | 
 
 | 
 
 | 
26
 | 
     SPEC: for ( my $index = 0; $index < @$extractspec; $index++ ) {  | 
| 
393
 | 
14
 | 
 
 | 
 
 | 
 
 | 
 
 | 
25
 | 
       my $spec = $extractspec->[ $index ];  | 
| 
394
 | 
14
 | 
 
 | 
 
 | 
 
 | 
 
 | 
21
 | 
       my ( $kstart, $kend ) = map $spec->{offset} + $_, @{ $spec->{keys} };  | 
| 
 
 | 
14
 | 
 
 | 
 
 | 
 
 | 
 
 | 
49
 | 
    | 
| 
395
 | 
14
 | 
 
 | 
 
 | 
 
 | 
 
 | 
39
 | 
       my $this_ids = [ @$array[ $kstart..$kend ] ];  | 
| 
396
 | 
14
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
37
 | 
       next SPEC if !grep defined, @$this_ids; # null PK = no result  | 
| 
397
 | 
14
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
37
 | 
       next SPEC if !_new_obj( $index2ids[ $index ], $this_ids );  | 
| 
398
 | 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
34
 | 
       _invalidate_ids( \@index2ids, $spec );  | 
| 
399
 | 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
22
 | 
       $index2ids[ $index ] = $this_ids;  | 
| 
400
 | 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
18
 | 
       my ( $fstart, $fend ) = map $spec->{offset} + $_, @{ $spec->{fields} };  | 
| 
 
 | 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
36
 | 
    | 
| 
401
 | 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
20
 | 
       my %hash;  | 
| 
402
 | 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
23
 | 
       @hash{ @{ $spec->{fieldnames} } } = @$array[ $fstart..$fend ];  | 
| 
 
 | 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
60
 | 
    | 
| 
403
 | 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
32
 | 
       _init_obj( \%hash, \@index2entrypoint, $spec );  | 
| 
404
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 #use Test::More; diag "efr ", explain [ $array, $fstart, $fend, \%hash ];  | 
| 
405
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
406
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   }  | 
| 
407
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
117
 | 
   @ret;  | 
| 
408
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
409
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
410
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub _invalidate_ids {  | 
| 
411
 | 
16
 | 
 
 | 
 
 | 
  
16
  
 | 
 
 | 
28
 | 
   my ( $index2ids, $spec ) = @_;  | 
| 
412
 | 
16
 | 
 
 | 
 
 | 
 
 | 
 
 | 
30
 | 
   $index2ids->[ $spec->{specsindex} ] = undef;  | 
| 
413
 | 
16
 | 
 
 | 
 
 | 
 
 | 
 
 | 
24
 | 
   _invalidate_ids( $index2ids, $_->[1] ) for @{ $spec->{subspecs} };  | 
| 
 
 | 
16
 | 
 
 | 
 
 | 
 
 | 
 
 | 
43
 | 
    | 
| 
414
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
415
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
416
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub _init_obj {  | 
| 
417
 | 
12
 | 
 
 | 
 
 | 
  
12
  
 | 
 
 | 
22
 | 
   my ( $obj, $index2entrypoint, $spec ) = @_;  | 
| 
418
 | 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
22
 | 
   my $entrypoint = $index2entrypoint->[ $spec->{specsindex} ];  | 
| 
419
 | 
12
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
27
 | 
   if ( $spec->{type} == 0 ) {  | 
| 
420
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # single, scalar-ref  | 
| 
421
 | 
2
 | 
 
 | 
 
 | 
 
 | 
 
 | 
3
 | 
     $$entrypoint = $obj;  | 
| 
422
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   } else {  | 
| 
423
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # multi, array-ref  | 
| 
424
 | 
10
 | 
 
 | 
 
 | 
 
 | 
 
 | 
18
 | 
     push @$entrypoint, $obj;  | 
| 
425
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   }  | 
| 
426
 | 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
18
 | 
   for ( @{ $spec->{subspecs} } ) {  | 
| 
 
 | 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
82
 | 
    | 
| 
427
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
     my ( $key, $subspec ) = @$_;  | 
| 
428
 | 
4
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
14
 | 
     $obj->{ $key } = ( $subspec->{type} == 0 ) ? undef : [];  | 
| 
429
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     $index2entrypoint->[ $subspec->{specsindex} ] = ( $subspec->{type} == 0 )  | 
| 
430
 | 
4
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
16
 | 
       ? \$obj->{ $key } : $obj->{ $key };  | 
| 
431
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   }  | 
| 
432
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
433
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
434
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # have we found a new object?  | 
| 
435
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # true if either array-ref false  | 
| 
436
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # false if all list members == each other  | 
| 
437
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub _new_obj {  | 
| 
438
 | 
14
 | 
 
 | 
 
 | 
  
14
  
 | 
 
 | 
33
 | 
   my ( $a1, $a2 ) = @_;  | 
| 
439
 | 
14
 | 
  
100
  
 | 
  
 66
  
 | 
 
 | 
 
 | 
51
 | 
   return 1 if !$a1 or !$a2;  | 
| 
440
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   # this might be quicker if could rely on numerical, therefore !=  | 
| 
441
 | 
5
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
28
 | 
   $a1->[ $_ ] ne $a2->[ $_ ] and return 1 for 0..$#$a1;  | 
| 
442
 | 
2
 | 
 
 | 
 
 | 
 
 | 
 
 | 
10
 | 
   0;  | 
| 
443
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
444
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
445
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 sub _build_dbspec {  | 
| 
446
 | 
1
 | 
 
 | 
 
 | 
  
1
  
 | 
 
 | 
13
 | 
   my ( $self ) = @_;  | 
| 
447
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
5
 | 
   my ( $dbcatalog, $dbschema ) = ( $self->dbcatalog, $self->dbschema );  | 
| 
448
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
12
 | 
   my $dbhgetter = $self->dbhgetter;  | 
| 
449
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
5
 | 
   my @table_names = @{ $dbhgetter->()->table_info(  | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
3
 | 
    | 
| 
450
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     $dbcatalog, $dbschema, undef, 'TABLE'  | 
| 
451
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   )->fetchall_arrayref( { TABLE_NAME => 1 } ) };  | 
| 
452
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   @table_names = grep $self->filter_table->($_), map $_->{TABLE_NAME},  | 
| 
453
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
548
 | 
     @table_names;  | 
| 
454
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
38
 | 
   s/\W//g for @table_names; # PostgreSQL quotes "user"  | 
| 
455
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
3
 | 
   my %dbspec;  | 
| 
456
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2
 | 
   for my $table ( @table_names ) {  | 
| 
457
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     # Pg returns undef if no FKs  | 
| 
458
 | 
5
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
6882
 | 
     next unless my $fk_sth = $dbhgetter->()->foreign_key_info(  | 
| 
459
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       undef, undef, undef, # PKT  | 
| 
460
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       $dbcatalog, $dbschema, $table, undef  | 
| 
461
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     );  | 
| 
462
 | 
5
 | 
 
 | 
  
 33
  
 | 
 
 | 
 
 | 
11724
 | 
     for (  | 
| 
463
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       grep $_->{PKTABLE_NAME} || $_->{UK_TABLE_NAME}, # mysql  | 
| 
464
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
25
 | 
       @{ $fk_sth->fetchall_arrayref( {} ) }  | 
| 
465
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     ) {  | 
| 
466
 | 
3
 | 
 
 | 
  
 33
  
 | 
 
 | 
 
 | 
1229
 | 
       my $totable = $_->{PKTABLE_NAME} || $_->{UK_TABLE_NAME};  | 
| 
467
 | 
3
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
       $totable =~ s/\W//g; # Pg again  | 
| 
468
 | 
3
 | 
 
 | 
  
 33
  
 | 
 
 | 
 
 | 
11
 | 
       my $fromkey = $_->{FKCOLUMN_NAME} || $_->{FK_COLUMN_NAME};  | 
| 
469
 | 
3
 | 
 
 | 
 
 | 
 
 | 
 
 | 
17
 | 
       (my $fromlabel = $fromkey) =~ s#_?id$##; # simple heuristic  | 
| 
470
 | 
3
 | 
 
 | 
  
 33
  
 | 
 
 | 
 
 | 
10
 | 
       my $tokey = $_->{PKCOLUMN_NAME} || $_->{UK_COLUMN_NAME};  | 
| 
471
 | 
3
 | 
 
 | 
 
 | 
 
 | 
 
 | 
18
 | 
       $dbspec{ $table }{ $fromlabel } = {  | 
| 
472
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         type => 'single',  | 
| 
473
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         fromkey => $fromkey, fromtable => $table,  | 
| 
474
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         totable => $totable, tokey => $tokey,  | 
| 
475
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       };  | 
| 
476
 | 
3
 | 
 
 | 
 
 | 
 
 | 
 
 | 
20
 | 
       $dbspec{ $totable }{ $self->multi_namer->( $table ) } = {  | 
| 
477
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         type => 'multi',  | 
| 
478
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         fromkey => $tokey, fromtable => $totable,  | 
| 
479
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
         totable => $table, tokey => $fromkey,  | 
| 
480
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
       };  | 
| 
481
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
     }  | 
| 
482
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   }  | 
| 
483
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
110
 | 
   \%dbspec;  | 
| 
484
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
485
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
486
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 1;  |