X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FStorage%2FDBI%2FOracle%2FGeneric.pm;h=37ddf7a799794052c0386e961f9d4f4e74b1cbf9;hb=2ba03b1627e18f422a84948277b72fd1c80da3a0;hp=a2b2f90bb989fdb946efbaed4a22fd0402591b9d;hpb=555cc3f40384239505aeec3fb41d6f862dd0c050;p=dbsrgits%2FDBIx-Class-Historic.git diff --git a/lib/DBIx/Class/Storage/DBI/Oracle/Generic.pm b/lib/DBIx/Class/Storage/DBI/Oracle/Generic.pm index a2b2f90..37ddf7a 100644 --- a/lib/DBIx/Class/Storage/DBI/Oracle/Generic.pm +++ b/lib/DBIx/Class/Storage/DBI/Oracle/Generic.pm @@ -2,6 +2,8 @@ package DBIx::Class::Storage::DBI::Oracle::Generic; use strict; use warnings; +use Scope::Guard (); +use Context::Preserve (); =head1 NAME @@ -15,6 +17,51 @@ DBIx::Class::Storage::DBI::Oracle::Generic - Oracle Support for DBIx::Class __PACKAGE__->set_primary_key('id'); __PACKAGE__->sequence('mysequence'); + # Somewhere in your Code + # add some data to a table with a hierarchical relationship + $schema->resultset('Person')->create ({ + firstname => 'foo', + lastname => 'bar', + children => [ + { + firstname => 'child1', + lastname => 'bar', + children => [ + { + firstname => 'grandchild', + lastname => 'bar', + } + ], + }, + { + firstname => 'child2', + lastname => 'bar', + }, + ], + }); + + # select from the hierarchical relationship + my $rs = $schema->resultset('Person')->search({}, + { + 'start_with' => { 'firstname' => 'foo', 'lastname' => 'bar' }, + 'connect_by' => { 'parentid' => { '-prior' => \'persionid' }, + 'order_siblings_by' => { -asc => 'name' }, + }; + ); + + # this will select the whole tree starting from person "foo bar", creating + # following query: + # SELECT + # me.persionid me.firstname, me.lastname, me.parentid + # FROM + # person me + # START WITH + # firstname = 'foo' and lastname = 'bar' + # CONNECT BY + # parentid = prior persionid + # ORDER SIBLINGS BY + # firstname ASC + =head1 DESCRIPTION This class implements base Oracle support. The subclass @@ -28,6 +75,24 @@ versions before 9. use base qw/DBIx::Class::Storage::DBI/; use mro 'c3'; +__PACKAGE__->sql_maker_class('DBIx::Class::SQLAHacks::Oracle'); + +sub deployment_statements { + my $self = shift;; + my ($schema, $type, $version, $dir, $sqltargs, @rest) = @_; + + $sqltargs ||= {}; + my $quote_char = $self->schema->storage->sql_maker->quote_char; + $sqltargs->{quote_table_names} = $quote_char ? 1 : 0; + $sqltargs->{quote_field_names} = $quote_char ? 1 : 0; + + my $oracle_version = eval { $self->_get_dbh->get_info(18) }; + + $sqltargs->{producer_args}{oracle_version} = $oracle_version; + + $self->next::method($schema, $type, $version, $dir, $sqltargs, @rest); +} + sub _dbh_last_insert_id { my ($self, $dbh, $source, @columns) = @_; my @ids = (); @@ -42,46 +107,42 @@ sub _dbh_last_insert_id { sub _dbh_get_autoinc_seq { my ($self, $dbh, $source, $col) = @_; - # look up the correct sequence automatically - my $sql = q{ - SELECT trigger_body FROM ALL_TRIGGERS t - WHERE t.table_name = ? - AND t.triggering_event = 'INSERT' - AND t.status = 'ENABLED' - }; - - # trigger_body is a LONG - local $dbh->{LongReadLen} = 64 * 1024 if ($dbh->{LongReadLen} < 64 * 1024); - - my $sth; + my $sql_maker = $self->sql_maker; my $source_name; - if ( ref $source->name ne 'SCALAR' ) { - $source_name = $source->name; + if ( ref $source->name eq 'SCALAR' ) { + $source_name = ${$source->name}; } else { - $source_name = ${$source->name}; + $source_name = $source->name; } + $source_name = uc($source_name) unless $sql_maker->quote_char; + + # trigger_body is a LONG + local $dbh->{LongReadLen} = 64 * 1024 if ($dbh->{LongReadLen} < 64 * 1024); + + # disable default bindtype + local $sql_maker->{bindtype} = 'normal'; + + # look up the correct sequence automatically + my ( $schema, $table ) = $source_name =~ /(\w+)\.(\w+)/; + my ($sql, @bind) = $sql_maker->select ( + 'ALL_TRIGGERS', + ['trigger_body'], + { + $schema ? (owner => $schema) : (), + table_name => $table || $source_name, + triggering_event => 'INSERT', + status => 'ENABLED', + }, + ); + my $sth = $dbh->prepare($sql); + $sth->execute (@bind); - # check for fully-qualified name (eg. SCHEMA.TABLENAME) - if ( my ( $schema, $table ) = $source_name =~ /(\w+)\.(\w+)/ ) { - $sql = q{ - SELECT trigger_body FROM ALL_TRIGGERS t - WHERE t.owner = ? AND t.table_name = ? - AND t.triggering_event = 'INSERT' - AND t.status = 'ENABLED' - }; - $sth = $dbh->prepare($sql); - $sth->execute( uc($schema), uc($table) ); - } - else { - $sth = $dbh->prepare($sql); - $sth->execute( uc( $source_name ) ); - } while (my ($insert_trigger) = $sth->fetchrow_array) { - return uc($1) if $insert_trigger =~ m!(\w+)\.nextval!i; # col name goes here??? + return $1 if $insert_trigger =~ m!("?\w+"?)\.nextval!i; # col name goes here??? } - $self->throw_exception("Unable to find a sequence INSERT trigger on table '" . $source->name . "'."); + $self->throw_exception("Unable to find a sequence INSERT trigger on table '$source_name'."); } sub _sequence_fetch { @@ -135,7 +196,7 @@ sub _dbh_execute { $self->throw_exception($exception) if $exception; - wantarray ? @res : $res[0] + $wantarray ? @res : $res[0] } =head2 get_autoinc_seq @@ -160,7 +221,7 @@ names to uppercase sub columns_info_for { my ($self, $table) = @_; - $self->next::method(uc($table)); + $self->next::method($table); } =head2 datetime_parser_type @@ -326,6 +387,146 @@ sub relname_to_table_alias { return $new_alias; } +=head2 with_deferred_fk_checks + +Runs a coderef between: + + alter session set constraints = deferred + ... + alter session set constraints = immediate + +to defer foreign key checks. + +Constraints must be declared C for this to work. + +=cut + +sub with_deferred_fk_checks { + my ($self, $sub) = @_; + + my $txn_scope_guard = $self->txn_scope_guard; + + $self->_do_query('alter session set constraints = deferred'); + + my $sg = Scope::Guard->new(sub { + $self->_do_query('alter session set constraints = immediate'); + }); + + return Context::Preserve::preserve_context(sub { $sub->() }, + after => sub { $txn_scope_guard->commit }); +} + +sub _select_args { + my ($self, $ident, $select, $where, $attrs) = @_; + + my $connect_by_args = {}; + if ( $attrs->{connect_by} || $attrs->{start_with} || $attrs->{order_siblings_by} || $attrs->{nocycle} ) { + $connect_by_args = { + connect_by => $attrs->{connect_by}, + nocycle => $attrs->{nocycle}, + start_with => $attrs->{start_with}, + order_siblings_by => $attrs->{order_siblings_by}, + } + } + + my @rv = $self->next::method($ident, $select, $where, $attrs); + + return (@rv, $connect_by_args); +} + +=head1 ATTRIBUTES + +Following additional attributes can be used in resultsets. + +=head2 connect_by + +=over 4 + +=item Value: \%connect_by + +=back + +A hashref of conditions used to specify the relationship between parent rows +and child rows of the hierarchy. + + connect_by => { parentid => 'prior personid' } + + # adds a connect by statement to the query: + # SELECT + # me.persionid me.firstname, me.lastname, me.parentid + # FROM + # person me + # CONNECT BY + # parentid = prior persionid + +=head2 nocycle + +=over 4 + +=item Value: [1|0] + +=back + +If you want to use NOCYCLE set to 1. + + connect_by => { parentid => 'prior personid' }, + nocycle => 1 + + # adds a connect by statement to the query: + # SELECT + # me.persionid me.firstname, me.lastname, me.parentid + # FROM + # person me + # CONNECT BY NOCYCLE + # parentid = prior persionid + + +=head2 start_with + +=over 4 + +=item Value: \%condition + +=back + +A hashref of conditions which specify the root row(s) of the hierarchy. + +It uses the same syntax as L + + start_with => { firstname => 'Foo', lastname => 'Bar' } + + # SELECT + # me.persionid me.firstname, me.lastname, me.parentid + # FROM + # person me + # START WITH + # firstname = 'foo' and lastname = 'bar' + # CONNECT BY + # parentid = prior persionid + +=head2 order_siblings_by + +=over 4 + +=item Value: ($order_siblings_by | \@order_siblings_by) + +=back + +Which column(s) to order the siblings by. + +It uses the same syntax as L + + 'order_siblings_by' => 'firstname ASC' + + # SELECT + # me.persionid me.firstname, me.lastname, me.parentid + # FROM + # person me + # CONNECT BY + # parentid = prior persionid + # ORDER SIBLINGS BY + # firstname ASC + =head1 AUTHOR See L.