File Coverage

blib/lib/DBIx/PgLink/Accessor/BaseAccessor.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package DBIx::PgLink::Accessor::BaseAccessor;
2              
3             # NOTE: accessor must be able to construct itself from local metadata
4             # even if remote connection is broken
5              
6 1     1   2636 use Carp;
  1         3  
  1         108  
7 1     1   576 use Moose;
  0            
  0            
8             use MooseX::Method;
9             use DBIx::PgLink::Local;
10             use DBIx::PgLink::Logger;
11              
12             extends 'Moose::Object';
13              
14             has 'connector' => (
15             is => 'ro',
16             isa => 'DBIx::PgLink::Connector',
17             required => 1,
18             weak_ref => 1,
19             );
20              
21             has 'building_mode' => (is=>'rw', isa=>'Bool', default=>0 );
22              
23             has 'object_id' => (
24             is => 'ro',
25             isa => 'Int',
26             required => 1,
27             lazy => 1,
28             default => sub {
29             my $self = shift;
30             if ($self->building_mode) {
31             return pg_dbh->selectrow_array(q/SELECT pg_catalog.nextval('dbix_pglink.object_id_sequence'::regclass)/);
32             } else {
33             confess 'Accessor metadata not loaded yet';
34             }
35             }
36             );
37              
38             # class method
39             sub metadata_table { 'dbix_pglink.objects' }
40             sub metadata_table_attr { {} } # attr for pg_dbh->prepare
41              
42             # shortcuts
43             sub adapter {
44             (shift)->connector->adapter;
45             }
46              
47             sub conn_name {
48             (shift)->connector->conn_name;
49             }
50              
51             # utility
52              
53             sub perl_quote {
54             my ($self, $str) = @_;
55             $str =~ s/\\/\\\\/g;
56             $str =~ s/'/\\'/g;
57             return "'$str'";
58             };
59              
60             sub abstract { confess "Abstract method called" }
61              
62              
63             # identifier quoting shortcuts
64              
65             sub QRI { # quote remote identifier
66             my $self = shift;
67             return $self->adapter->quote_identifier(@_);
68             }
69              
70             sub QRIS { # quote remote identifier with schema (and catalog)
71             my ($self, $name) = @_;
72             if ($self->adapter->include_catalog_to_qualified_name) {
73             return $self->adapter->quote_identifier($self->remote_catalog, $self->remote_schema, $name);
74             } elsif ($self->adapter->include_schema_to_qualified_name) {
75             return $self->adapter->quote_identifier($self->remote_schema, $name);
76             } else {
77             return $self->adapter->quote_identifier($name);
78             }
79             }
80              
81             sub QLI { # quote local identifier
82             my $self = shift;
83             return pg_dbh->quote_identifier(@_);
84             }
85              
86             sub QLIS { # quote local identifier with schema
87             my ($self, $name) = @_;
88             return pg_dbh->quote_identifier($self->local_schema, $name);
89             }
90              
91              
92             # NAMES
93              
94             has 'remote_object_type' => (is=>'ro', isa=>'Str', required=>1);
95             has 'remote_catalog' => (is=>'ro', isa=>'StrNull', required=>0);
96             has 'remote_schema' => (is=>'ro', isa=>'StrNull', required=>0);
97             has 'remote_object' => (is=>'ro', isa=>'Str', required=>1);
98              
99             has 'local_schema' => (is=>'ro', isa=>'Str', required=>1);
100             has 'local_object' => (is=>'ro', isa=>'Str', required=>1);
101              
102             # full qualified, double-quoted name
103             has 'local_schema_quoted' => (is=>'rw', isa=>'Str', lazy=>1, default=>sub{ $_[0]->QLI($_[0]->local_schema) } );
104             has 'local_object_quoted' => (is=>'rw', isa=>'Str', lazy=>1, default=>sub{ $_[0]->QLIS($_[0]->local_object) } );
105             has 'remote_object_quoted' => (is=>'rw', isa=>'Str', lazy=>1, default=>sub{ $_[0]->QRIS($_[0]->remote_object) } );
106              
107              
108             has 'old_accessor' => (is=>'rw', isa=>'DBIx::PgLink::Accessor::BaseAccessor');
109              
110             has 'skip_on_errors' => (is=>'ro', isa=>'ArrayRef', auto_deref=>1,
111             default=>sub{ ['cannot drop .* because other objects depend on it']}
112             );
113              
114             # -------------------------------------------------------
115              
116              
117             method build => named (
118             use_local_metadata => { isa => 'Bool', default=> 0 },
119             ) => sub {
120             my ($self, $p) = @_;
121              
122             $self->building_mode(1);
123              
124             trace_msg('INFO', "Building accessor for " . $self->remote_object_type . " " . $self->remote_object_quoted)
125             if trace_level >= 1;
126              
127             my $savepoint_name = 'build_' . $self->object_id; # unique
128             pg_dbh->do("SAVEPOINT $savepoint_name");
129             eval {
130              
131             $self->load_old_accessor;
132              
133             unless ($p->{use_local_metadata}) {
134             $self->create_metadata;
135            
136             $self->delete_metadata_by_id( $self->old_accessor->object_id ) if $self->old_accessor;
137              
138             $self->save_metadata;
139             }
140              
141             $self->create_local_schema;
142              
143             $self->old_accessor->drop_local_objects if $self->old_accessor;
144              
145             $self->create_local_objects;
146              
147             };
148             if ($@) {
149             my $err = $@;
150             for my $skip ($self->skip_on_errors) {
151             if ($err =~ /$skip/) {
152             # do not raise exception, issue warning and skip this object
153             pg_dbh->do("ROLLBACK TO SAVEPOINT $savepoint_name");
154             trace_msg('WARNING', "Cannot create accessor for "
155             . $self->remote_object_type . " " . $self->remote_object_quoted
156             . ". Error: " . $err);
157             return 0;
158             }
159             }
160             die $@;
161             }
162             pg_dbh->do("RELEASE SAVEPOINT $savepoint_name");
163              
164             return 1;
165             };
166              
167              
168             sub create_metadata { abstract() }
169             sub drop_local_objects { abstract() }
170             sub create_local_objects { abstract() }
171              
172              
173             sub load_old_accessor {
174             my $self = shift;
175              
176             # load metadata for previous version of same remote object
177             my $old_meta = $self->load_metadata_by_remote_name;
178             $self->old_accessor(
179             $old_meta
180             ? $self->new( %{$old_meta}, connector=>$self->connector )
181             : undef
182             );
183             }
184              
185              
186             # constructor
187             method load => named (
188             connector => { isa=>'DBIx::PgLink::Connector', required=>1},
189             object_id => { isa=>'Int', required=>1},
190             ) => sub {
191             my ($class, $p) = @_;
192              
193             my $data = pg_dbh->selectrow_hashref(<<END_OF_SQL,
194             SELECT *
195             FROM @{[ $class->metadata_table ]}
196             WHERE object_id = \$1
197             END_OF_SQL
198             {
199             %{$class->metadata_table_attr},
200             Slice => {},
201             no_cursor=>1,
202             types=>[qw/INT4/],
203             },
204             $p->{object_id},
205             )
206             or confess "Cannot load accessor metadata with id=$p->{object_id}";
207             return $class->new( %{$data}, connector => $p->{connector} );
208             };
209              
210              
211             method delete_metadata_by_id => positional(
212             {isa=>'Int', required=>1},
213             ) => sub {
214             my ($self, $object_id) = @_;
215              
216             # delete base row by id
217             # foreign key cascade to child metadata (columns, queries, etc)
218             pg_dbh->do(<<'END_OF_SQL',
219             DELETE FROM dbix_pglink.objects
220             WHERE object_id = $1
221             END_OF_SQL
222             {types=>[qw/INT4/]},
223             $object_id,
224             );
225             };
226              
227             sub load_metadata_by_local_name {
228             my $self = shift;
229              
230             # load row by natural key
231             return pg_dbh->selectrow_hashref(<<END_OF_SQL,
232             SELECT *
233             FROM @{[ $self->metadata_table ]}
234             WHERE conn_name = \$1
235             and remote_object_type = \$2
236             and local_schema = \$3
237             and local_object = \$4
238             END_OF_SQL
239             {
240             %{$self->metadata_table_attr},
241             no_cursor=>1,
242             types=>[qw/TEXT TEXT TEXT TEXT/],
243             },
244             $self->conn_name,
245             $self->remote_object_type,
246             $self->local_schema,
247             $self->local_object,
248             );
249             }
250              
251              
252             sub load_metadata_by_remote_name {
253             my $self = shift;
254             # find row by natural key (remote schema+name + local schema+name)
255             # one remote table can have many accessors in different local schemas
256             # compare object class instead type (remote TABLE can become VIEW)
257             return pg_dbh->selectrow_hashref(<<END_OF_SQL,
258             SELECT *
259             FROM @{[ $self->metadata_table ]}
260             WHERE conn_name = \$1
261             and dbix_pglink.object_type_class(remote_object_type) = dbix_pglink.object_type_class(\$2)
262             and remote_catalog is not distinct from \$3
263             and remote_schema is not distinct from \$4
264             and remote_object = \$5
265             and local_schema = \$6
266             and local_object = \$7
267             END_OF_SQL
268             {
269             %{$self->metadata_table_attr},
270             types=>[qw/TEXT TEXT TEXT TEXT TEXT TEXT TEXT/],
271             # 1 2 3 4 5 6 7
272             },
273             $self->conn_name, # 1
274             $self->remote_object_type, # 2
275             $self->remote_catalog, # 3
276             $self->remote_schema, # 4
277             $self->remote_object, # 5
278             $self->local_schema, # 6
279             $self->local_object, # 7
280             );
281             }
282              
283              
284             sub save_metadata {
285             my $self = shift;
286              
287             # just base table, not $self->metadata_table
288             pg_dbh->do(<<'END_OF_SQL',
289             INSERT INTO dbix_pglink.objects (
290             object_id, --1
291             conn_name, --2
292             remote_object_type, --3
293             remote_catalog, --4
294             remote_schema, --5
295             remote_object, --6
296             local_schema, --7
297             local_object --8
298             ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
299             END_OF_SQL
300             {types=>[qw/INT4 TEXT TEXT TEXT TEXT TEXT TEXT TEXT/]},
301             # 1 2 3 4 5 6 7 8
302             $self->object_id, # 1
303             $self->conn_name, # 2
304             $self->remote_object_type, # 3
305             $self->remote_catalog, # 4
306             $self->remote_schema, # 5
307             $self->remote_object, # 6
308             $self->local_schema, # 7
309             $self->local_object, # 8
310             );
311             };
312              
313              
314             sub create_local_schema {
315             my $self = shift;
316              
317             return if pg_dbh->selectrow_array(<<'END_OF_SQL', {}, $self->local_schema);
318             SELECT 1
319             FROM information_schema.schemata
320             WHERE schema_name = $1
321             END_OF_SQL
322              
323             my $local_schema_quoted = pg_dbh->quote_identifier($self->local_schema);
324             pg_dbh->do("CREATE SCHEMA $local_schema_quoted");
325             trace_msg("NOTICE", "Created schema $local_schema_quoted")
326             if trace_level >= 1;
327             };
328              
329              
330             method create_comment => named (
331             type => { isa => 'Str', required => 1},
332             name => { isa => 'Str', required => 1}, # quoted full name
333             comment => { isa => 'Str', required => 1},
334             ) => sub {
335             my ($self, $p) = @_;
336             #trim starting/ending newlines
337             $p->{comment} =~ s/^\n+//;
338             $p->{comment} =~ s/\n+$//;
339             $p->{comment} .= " at " . $self->conn_name;
340             pg_dbh->do("COMMENT ON $p->{type} $p->{name} IS " . pg_dbh->quote($p->{comment}));
341             };
342              
343              
344             # drop accessor object and metadata
345             sub drop {
346             my $self = shift;
347             $self->drop_local_objects;
348             $self->delete_metadata(1);
349             }
350              
351              
352              
353             # ------------ enumeration (class methods) ------------------------------------
354              
355              
356             # class method
357             sub get_accessor_type {
358             my ($class, $object_id) = @_;
359             return pg_dbh->selectrow_array(<<'END_OF_SQL',
360             SELECT remote_object_type
361             FROM dbix_pglink.objects
362             WHERE object_id = $1
363             END_OF_SQL
364             { no_cursor=>1, types=>[qw/INT4/] },
365             $object_id,
366             );
367             }
368              
369              
370             # class method
371             # interface with params defaults and requirements
372             # (override/around/inner got raw params, not cooked by MooseX::Method)
373             method build_accessors => named (
374             connector => { isa => 'DBIx::PgLink::Connector', required => 1 },
375             local_schema => { isa => 'Str', required => 1 },
376             remote_catalog => { isa => 'StrNull', default => '%' },
377             remote_schema => { isa => 'StrNull', default => '%' },
378             remote_object => { isa => 'Str', default => '%' },
379             remote_object_type => { isa => 'Str', required => 1 },
380             object_name_mapping => { isa => 'HashRef', required => 0 },
381             ) => sub {
382             my ($class, $p) = @_;
383              
384             my $cnt = $class->_implement_build_accessors($p);
385             trace_msg('INFO', "Created $cnt accessor(s) for remote $p->{remote_object_type}") if trace_level >= 0;
386             return $cnt;
387             };
388              
389              
390             # class method, no params check
391             sub _implement_build_accessors { abstract() }
392              
393              
394             # class method
395             method rebuild_accessors => named (
396             connector => { isa => 'DBIx::PgLink::Connector', required => 1 },
397             remote_object_type => { isa => 'Str', required => 1 },
398             local_schema => { isa => 'Str', required => 1 },
399             local_object => { isa => 'Str', default => '%' },
400             ) => sub {
401             my ($class, $p) = @_;
402              
403             my $cnt = $class->for_accessors(
404             %{$p},
405             coderef => sub {
406             (shift)->build(
407             use_local_metadata => 1
408             );
409             }
410             );
411             trace_msg('INFO', "Recreated $cnt accessor(s) $p->{remote_object_type}") if trace_level >= 0;
412             return $cnt;
413             };
414              
415              
416             # class method
417             method for_accessors => named (
418             connector => { isa => 'DBIx::PgLink::Connector', required => 1 },
419             remote_object_type => { isa => 'Str', required => 1 },
420             local_schema => { isa => 'Str', default => '%' }, # like
421             local_object => { isa => 'Str', default => '%' }, # like
422             coderef => { isa => 'CodeRef', required => 1 },
423             ) => sub {
424             my ($class, $p) = @_;
425              
426             my $sth = pg_dbh->prepare_cached(<<'END_OF_SQL');
427             SELECT object_id
428             FROM dbix_pglink.objects
429             WHERE conn_name = $1
430             and remote_object_type = $2
431             and local_schema like $3
432             and local_object like $4
433             END_OF_SQL
434              
435             $sth->execute(
436             $p->{connector}->conn_name,
437             $p->{remote_object_type},
438             $p->{local_schema},
439             $p->{local_object},
440             );
441              
442             my $cnt = 0;
443             while (my $row = $sth->fetchrow_hashref) {
444             my $accessor = $class->load(
445             connector => $p->{connector},
446             object_id => $row->{object_id},
447             );
448             $p->{coderef}->($accessor);
449             $cnt++;
450             }
451             return $cnt;
452             };
453              
454             # -------------------------------------------------------
455              
456              
457             __PACKAGE__->meta->make_immutable;
458              
459             1;