Merge 'trunk' into 'DBIx-Class-current'
Matt S Trout [Tue, 28 Mar 2006 22:22:01 +0000 (22:22 +0000)]
r9255@obrien (orig r1356):  bluefeet | 2006-03-25 02:28:25 +0000
Small doc typo fix in ResultSet.
r9256@obrien (orig r1357):  jguenther | 2006-03-25 08:43:38 +0000
changed Returns -> Return Value
r9257@obrien (orig r1358):  jguenther | 2006-03-25 08:49:58 +0000
removed parentheses from argument lists in docs
r9260@obrien (orig r1359):  matthewt | 2006-03-25 18:08:38 +0000
0.06000 changes
r9341@obrien (orig r1362):  ningu | 2006-03-26 00:15:00 +0000
various small doc fixes
r9342@obrien (orig r1363):  ningu | 2006-03-26 00:16:07 +0000
missed a couple things
r9344@obrien (orig r1365):  blblack | 2006-03-26 15:44:25 +0100
converted tabs to spaces, removed trailing whitespace
r9349@obrien (orig r1370):  matthewt | 2006-03-27 18:35:26 +0100
Quoting fixes for single-table ops
r9352@obrien (orig r1372):  matthewt | 2006-03-27 21:09:09 +0100
Fix typo in from rs attr docs
r9365@obrien (orig r1377):  nothingmuch | 2006-03-27 23:21:27 +0100
split( ";\n", @statements" ) returns crack
r9368@obrien (orig r1380):  bricas | 2006-03-28 14:04:30 +0100
minor pod fixes
r9371@obrien (orig r1381):  castaway | 2006-03-28 15:37:34 +0100
More debugging for "no sth generated"

r9372@obrien (orig r1382):  sszabo | 2006-03-28 19:28:37 +0100
Changed logic for determining foreign key constraints
in SQL::Translator::Parser::DBIx::Class to compare
self keys against the primary key.

Made SQL::Translator::Parser::DBIx::Class handle
multi-column foreign key constraints.

Added tests on helperrels for these.

r9373@obrien (orig r1383):  sszabo | 2006-03-28 19:54:35 +0100
Add missing ) before unless.

21 files changed:
Changes
lib/DBIx/Class.pm
lib/DBIx/Class/CDBICompat.pm
lib/DBIx/Class/Core.pm
lib/DBIx/Class/Cursor.pm
lib/DBIx/Class/Relationship/Base.pm
lib/DBIx/Class/ResultSet.pm
lib/DBIx/Class/ResultSource.pm
lib/DBIx/Class/Row.pm
lib/DBIx/Class/Schema.pm
lib/DBIx/Class/Serialize/Storable.pm
lib/DBIx/Class/Storage/DBI.pm
lib/DBIx/Class/Storage/DBI/Cursor.pm
lib/DBIx/Class/UUIDMaker/Win32/Guidgen.pm
lib/DBIx/Class/UUIDMaker/Win32API/GUID.pm
lib/SQL/Translator/Parser/DBIx/Class.pm
t/02pod.t
t/19quotes.t
t/helperrels/26sqlt.t [new file with mode: 0644]
t/lib/DBICTest/Schema.pm
t/lib/DBICTest/Schema/TwoKeyTreeLike.pm [new file with mode: 0644]

diff --git a/Changes b/Changes
index 53d01f9..7f51a67 100644 (file)
--- a/Changes
+++ b/Changes
@@ -1,6 +1,11 @@
 Revision history for DBIx::Class
 
-0.05999_05
+0.06001
+        - Added fix for quoting with single table
+
+0.06000
+        - Lots of documentation improvements
+        - Minor tweak to related_resultset to prevent it storing a searched rs
         - Fixup to columns_info_for when database returns type(size)
         - Made do_txn respect void context (on the off-chance somebody cares)
         - Fix exception text for nonexistent key in ResultSet::find()
@@ -37,6 +42,7 @@ Revision history for DBIx::Class
         - $schema->deploy
         - HAVING support
         - prefetch for has_many
+        - cache attr for resultsets
         - PK::Auto::* no longer required since Storage::DBI::* handle auto-inc
         - minor tweak to tests for join edge case
         - added cascade_copy relationship attribute
index 7c659df..64bec50 100644 (file)
@@ -13,7 +13,7 @@ sub component_base_class { 'DBIx::Class' }
 # i.e. first release of 0.XX *must* be 0.XX000. This avoids fBSD ports
 # brain damage and presumably various other packaging systems too
 
-$VERSION = '0.05999_04';
+$VERSION = '0.06000';
 
 sub MODIFY_CODE_ATTRIBUTES {
     my ($class,$code,@attrs) = @_;
@@ -38,7 +38,7 @@ DBIx::Class - Extensible and flexible object <-> relational mapper.
 
 =head1 SYNOPSIS
 
-Create a base schema class called DB/Main.pm:
+Create a schema class called DB/Main.pm:
 
   package DB::Main;
   use base qw/DBIx::Class::Schema/;
@@ -47,7 +47,7 @@ Create a base schema class called DB/Main.pm:
 
   1;
 
-Create a class to represent artists, who have many CDs, in DB/Main/Artist.pm:
+Create a table class to represent artists, who have many CDs, in DB/Main/Artist.pm:
 
   package DB::Main::Artist;
   use base qw/DBIx::Class/;
@@ -56,52 +56,56 @@ Create a class to represent artists, who have many CDs, in DB/Main/Artist.pm:
   __PACKAGE__->table('artist');
   __PACKAGE__->add_columns(qw/ artistid name /);
   __PACKAGE__->set_primary_key('artistid');
-  __PACKAGE__->has_many('cds' => 'DB::Main::CD');
+  __PACKAGE__->has_many(cds => 'DB::Main::CD');
 
   1;
 
-A class to represent a CD, which belongs to an artist, in DB/Main/CD.pm:
+A table class to represent a CD, which belongs to an artist, in DB/Main/CD.pm:
 
   package DB::Main::CD;
   use base qw/DBIx::Class/;
 
   __PACKAGE__->load_components(qw/PK::Auto Core/);
   __PACKAGE__->table('cd');
-  __PACKAGE__->add_columns(qw/ cdid artist title year/);
+  __PACKAGE__->add_columns(qw/ cdid artist title year /);
   __PACKAGE__->set_primary_key('cdid');
-  __PACKAGE__->belongs_to('artist' => 'DB::Main::Artist');
+  __PACKAGE__->belongs_to(artist => 'DB::Main::Artist');
 
   1;
 
 Then you can use these classes in your application's code:
 
   # Connect to your database.
-  my $ds = DB::Main->connect(@dbi_dsn);
+  use DB::Main;
+  my $schema = DB::Main->connect($dbi_dsn, $user, $pass, \%dbi_params);
 
   # Query for all artists and put them in an array,
   # or retrieve them as a result set object.
-  my @all_artists = $ds->resultset('Artist')->all;
-  my $all_artists_rs = $ds->resultset('Artist');
+  my @all_artists = $schema->resultset('Artist')->all;
+  my $all_artists_rs = $schema->resultset('Artist');
 
   # Create a result set to search for artists.
   # This does not query the DB.
-  my $johns_rs = $ds->resultset('Artist')->search(
-    # Build your WHERE using an SQL::Abstract structure:
-    { 'name' => { 'like', 'John%' } }
+  my $johns_rs = $schema->resultset('Artist')->search(
+    # Build your WHERE using an L<SQL::Abstract> structure:
+    { name => { like => 'John%' } }
   );
 
-  # This executes a joined query to get the cds
+  # Execute a joined query to get the cds.
   my @all_john_cds = $johns_rs->search_related('cds')->all;
 
-  # Queries but only fetches one row so far.
+  # Fetch only the next row.
   my $first_john = $johns_rs->next;
 
+  # Specify ORDER BY on the query.
   my $first_john_cds_by_title_rs = $first_john->cds(
     undef,
     { order_by => 'title' }
   );
 
-  my $millennium_cds_rs = $ds->resultset('CD')->search(
+  # Create a result set that will fetch the artist relationship
+  # at the same time as it fetches CDs, using only one query.
+  my $millennium_cds_rs = $schema->resultset('CD')->search(
     { year => 2000 },
     { prefetch => 'artist' }
   );
@@ -109,12 +113,12 @@ Then you can use these classes in your application's code:
   my $cd = $millennium_cds_rs->next; # SELECT ... FROM cds JOIN artists ...
   my $cd_artist_name = $cd->artist->name; # Already has the data so no query
 
-  my $new_cd = $ds->resultset('CD')->new({ title => 'Spoon' });
+  my $new_cd = $schema->resultset('CD')->new({ title => 'Spoon' });
   $new_cd->artist($cd->artist);
   $new_cd->insert; # Auto-increment primary key filled in after INSERT
   $new_cd->title('Fork');
 
-  $ds->txn_do(sub { $new_cd->update }); # Runs the update in a transaction
+  $schema->txn_do(sub { $new_cd->update }); # Runs the update in a transaction
 
   $millennium_cds_rs->update({ year => 2002 }); # Single-query bulk update
 
@@ -130,27 +134,26 @@ JOIN, LEFT JOIN, COUNT, DISTINCT, GROUP BY and HAVING support.
 
 DBIx::Class can handle multi-column primary and foreign keys, complex
 queries and database-level paging, and does its best to only query the
-database when it actually needs to in order to return something you've directly
-asked for. If a resultset is used as an iterator it only fetches rows off
-the statement handle as requested in order to minimise memory usage. It
-has auto-increment support for SQLite, MySQL, PostgreSQL, Oracle, SQL
-Server and DB2 and is known to be used in production on at least the first
-four, and is fork- and thread-safe out of the box (although your DBD may not
-be). 
+database in order to return something you've directly asked for. If a
+resultset is used as an iterator it only fetches rows off the statement
+handle as requested in order to minimise memory usage. It has auto-increment
+support for SQLite, MySQL, PostgreSQL, Oracle, SQL Server and DB2 and is
+known to be used in production on at least the first four, and is fork-
+and thread-safe out of the box (although your DBD may not be).
 
 This project is still under rapid development, so features added in the
-latest major release may not work 100% yet - check the Changes if you run
+latest major release may not work 100% yet -- check the Changes if you run
 into trouble, and beware of anything explicitly marked EXPERIMENTAL. Failing
 test cases are *always* welcome and point releases are put out rapidly as
 bugs are found and fixed.
 
 Even so, we do our best to maintain full backwards compatibility for published
-APIs since DBIx::Class is used in production in a number of organisations;
-the test suite is now fairly substantial and several developer releases are
+APIs, since DBIx::Class is used in production in a number of organisations.
+The test suite is quite substantial, and several developer releases are
 generally made to CPAN before the -current branch is merged back to trunk for
 a major release.
 
-The community can be found via -
+The community can be found via:
 
   Mailing list: http://lists.rawmode.org/mailman/listinfo/dbix-class/
 
@@ -236,6 +239,8 @@ konobi: Scott McWhirter
 
 scotty: Scotty Allen <scotty@scottyallen.com>
 
+sszabo: Stephan Szabo <sszabo@bigpanda.com>
+
 Todd Lipcon
 
 wdh: Will Hawes
index 07d80c1..874c4c7 100644 (file)
@@ -68,7 +68,7 @@ provided it looks something like this:
     CDBICompat::MightHave
   /);
 
-=head1 Components
+=head1 COMPONENTS
 
 =over 4
 
@@ -94,12 +94,8 @@ Allows you to turn on automatic updates for column values.
 
 =item HasA
 
-Responsible for HasA relationships. 
-
 =item HasMany
 
-Responsible for HasMany relationships. 
-
 =item ImaDBI
 
 =item LazyLoading
@@ -111,8 +107,6 @@ in the perl interpreter.
 
 =item MightHave
 
-Responsible for MightHave relationships. 
-
 =item ObjIndexStubs
 
 =item ReadOnly
@@ -125,8 +119,6 @@ Responsible for MightHave relationships.
 
 =item Triggers
 
-This class implements the trigger functionality.
-
 =item PassThrough
 
 =back
index e8b14ef..96a6a9a 100644 (file)
@@ -35,6 +35,8 @@ The core modules currently are:
 
 =over 4
 
+=item L<DBIx::Class::Serialize::Storable>
+
 =item L<DBIx::Class::InflateColumn>
 
 =item L<DBIx::Class::Relationship>
index 065e195..3c55b69 100644 (file)
@@ -28,8 +28,6 @@ information.
 
 =head2 new
 
-=back
-
 Virtual method. Returns a new L<DBIx::Class::Cursor> object.
 
 =cut
@@ -40,8 +38,6 @@ sub new {
 
 =head2 next
 
-=back
-
 Virtual method. Advances the cursor to the next row.
 
 =cut
@@ -52,8 +48,6 @@ sub next {
 
 =head2 reset
 
-=back
-
 Virtual method. Resets the cursor to the beginning.
 
 =cut
@@ -64,8 +58,6 @@ sub reset {
 
 =head2 all
 
-=back
-
 Virtual method. Returns all rows in the L<DBIx::Class::ResultSet>.
 
 =cut
index 3ab412d..6e6a4f4 100644 (file)
@@ -23,7 +23,7 @@ methods, for predefined ones, look in L<DBIx::Class::Relationship>.
 
 =over 4
 
-=item Arguments: ('relname', 'Foreign::Class', $cond, $attrs)
+=item Arguments: 'relname', 'Foreign::Class', $cond, $attrs
 
 =back
 
@@ -92,7 +92,7 @@ created, which calls C<create_related> for the relationship.
 
 =over 4
 
-=item Arguments: ($relname, $rel_info)
+=item Arguments: $relname, $rel_info
 
 =back
 
@@ -107,9 +107,9 @@ sub register_relationship { }
 
 =over 4
 
-=item Arguments: ($relationship_name)
+=item Arguments: $relationship_name
 
-=item Returns: $related_resultset
+=item Return Value: $related_resultset
 
 =back
 
index 41dbfb4..c012011 100644 (file)
@@ -59,8 +59,6 @@ In the examples below, the following table classes are used:
 
 =item Return Value: $rs
 
-=item 
-
 =back
 
 The resultset constructor. Takes a source object (usually a
@@ -186,7 +184,7 @@ sub new {
                  # year = 2005 OR year = 2004
 
 If you need to pass in additional attributes but no additional condition,
-call it as C<search(undef, \%attrs);>.
+call it as C<search(undef, \%attrs)>.
 
   # "SELECT name, artistid FROM $artist_table"
   my @all_artists = $schema->resultset('Artist')->search(undef, {
@@ -267,7 +265,7 @@ sub search_literal {
 
 =over 4
 
-=item Arguments: (@values | \%cols), \%attrs?
+=item Arguments: @values | \%cols, \%attrs?
 
 =item Return Value: $row_object
 
@@ -347,7 +345,7 @@ sub find {
     name => 'Emo-R-Us',
   });
 
-Search the specified relationship, optionally specify a condition and
+Searches the specified relationship, optionally specifying a condition and
 attributes for matching records. See L</ATTRIBUTES> for more information.
 
 =cut
@@ -430,7 +428,7 @@ sub single {
   # WHERE title LIKE '%blue%'
   $cd_rs = $rs->search_like({ title => '%blue%'});
 
-Perform a search, but use C<LIKE> instead of C<=> as the condition. Note
+Performs a search, but uses C<LIKE> instead of C<=> as the condition. Note
 that this is simply a convenience method. You most likely want to use
 L</search> with specific operators.
 
@@ -457,8 +455,8 @@ sub search_like {
 =back
 
 Returns a resultset or object list representing a subset of elements from the
-resultset slice is called on.  Indexes are from 0 - i.e. to get the first
-three records, call
+resultset slice is called on. Indexes are from 0, i.e., to get the first
+three records, call:
 
   my ($one, $two, $three) = $rs->slice(0, 2);
 
@@ -617,7 +615,7 @@ is derived.
 
 =over 4
 
-=item Arguments: ($cond, \%attrs?)?
+=item Arguments: $cond, \%attrs??
 
 =item Return Value: $count
 
@@ -772,7 +770,7 @@ sub reset {
 =back
 
 Resets the resultset and returns an object for the first result (if the
-resultset contains anything).
+resultset returns anything).
 
 =cut
 
@@ -815,8 +813,8 @@ sub update {
 
 =back
 
-Fetches all objects and updates them one at a time.  Note that C<update_all>
-will run cascade triggers while L</update> will not.
+Fetches all objects and updates them one at a time. Note that C<update_all>
+will run DBIC cascade triggers, while L</update> will not.
 
 =cut
 
@@ -841,7 +839,8 @@ sub update_all {
 =back
 
 Deletes the contents of the resultset from its result source. Note that this
-will not run cascade triggers. See L</delete_all> if you need triggers to run.
+will not run DBIC cascade triggers. See L</delete_all> if you need triggers
+to run.
 
 =cut
 
@@ -899,8 +898,8 @@ sub delete {
 
 =back
 
-Fetches all objects and deletes them one at a time.  Note that C<delete_all>
-will run cascade triggers while L</delete> will not.
+Fetches all objects and deletes them one at a time. Note that C<delete_all>
+will run DBIC cascade triggers, while L</delete> will not.
 
 =cut
 
@@ -947,7 +946,7 @@ sub pager {
 
 Returns a resultset for the $page_number page of the resultset on which page
 is called, where each page contains a number of rows equal to the 'rows'
-attribute set on the resultset, or 10 by default
+attribute set on the resultset (10 by default).
 
 =cut
 
@@ -1069,9 +1068,9 @@ sub find_or_create {
 
   $class->update_or_create({ col => $val, ... });
 
-First, search for an existing row matching one of the unique constraints
-(including the primary key) on the source of this resultset.  If a row is
-found, update it with the other given column values.  Otherwise, create a new
+First, searches for an existing row matching one of the unique constraints
+(including the primary key) on the source of this resultset. If a row is
+found, updates it with the other given column values. Otherwise, creates a new
 row.
 
 Takes an optional C<key> attribute to search on a specific unique constraint.
@@ -1090,7 +1089,7 @@ For example:
 If no C<key> is specified, it searches on all unique constraints defined on the
 source, including the primary key.
 
-If the C<key> is specified as C<primary>, search only on the primary key.
+If the C<key> is specified as C<primary>, it searches only on the primary key.
 
 See also L</find> and L</find_or_create>.
 
@@ -1140,7 +1139,7 @@ sub update_or_create {
 
 =back
 
-Gets the contents of the cache for the resultset if the cache is set
+Gets the contents of the cache for the resultset, if the cache is set.
 
 =cut
 
@@ -1263,6 +1262,8 @@ overview of them:
 
 =item Value: ($order_by | \@order_by)
 
+=back
+
 Which column(s) to order the results by. This is currently passed
 through directly to SQL, so you can give e.g. C<year DESC> for a
 descending order on the column `year'.
@@ -1471,7 +1472,7 @@ avoid using C<from> unless you cannot achieve the desired result using C<join>.
 In simple terms, C<from> works as follows:
 
     [
-        { <alias> => <table>, -join-type => 'inner|left|right' }
+        { <alias> => <table>, -join_type => 'inner|left|right' }
         [] # nested JOIN (optional)
         { <table.column> => <foreign_table.foreign_key> }
     ]
@@ -1534,7 +1535,7 @@ with a father in the person table, we could explicitly use C<INNER JOIN>:
             from => [
                 { child => 'person' },
                 [
-                    { father => 'person', -join-type => 'inner' },
+                    { father => 'person', -join_type => 'inner' },
                     { 'father.id' => 'child.father_id' }
                 ],
             ]
index 8b27139..68d16b2 100644 (file)
@@ -54,13 +54,12 @@ sub new {
 
   $table->add_columns('col1' => \%col1_info, 'col2' => \%col2_info, ...);
 
-Adds columns to the result source. If supplied key => hashref pairs uses
-the hashref as the column_info for that column.
+Adds columns to the result source. If supplied key => hashref pairs, uses
+the hashref as the column_info for that column. Repeated calls of this
+method will add more columns, not replace them.
 
-Repeated calls of this method will add more columns, not replace them.
-
-The contents of the column_info are not set in stone, the following
-keys are currently recognised/used by DBIx::Class. 
+The contents of the column_info are not set in stone. The following
+keys are currently recognised/used by DBIx::Class:
 
 =over 4
 
@@ -71,15 +70,15 @@ the name of the column will be used.
 
 =item data_type
 
-This contains the column type, it is automatically filled by the
+This contains the column type. It is automatically filled by the
 L<SQL::Translator::Producer::DBIx::Class::File> producer, and the
-L<DBIx::Class::Schema::Loader> module. If you do not enter the
+L<DBIx::Class::Schema::Loader> module. If you do not enter a
 data_type, DBIx::Class will attempt to retrieve it from the
-database for you, using L<DBI>s column_info method. The values of this
+database for you, using L<DBI>'s column_info method. The values of this
 key are typically upper-cased.
 
-Currently there is no standard set of values for the data_type, use
-whatever your database(s) support.
+Currently there is no standard set of values for the data_type. Use
+whatever your database supports.
 
 =item size
 
@@ -88,31 +87,32 @@ restriction. This is currently not used by DBIx::Class.
 
 =item is_nullable
 
-If the column is allowed to contain NULL values, set a true value
-(typically 1), here. This is currently not used by DBIx::Class.
+Set this to a true value for a columns that is allowed to contain
+NULL values. This is currently not used by DBIx::Class.
 
 =item is_auto_increment
 
-Set this to a true value if this is a column that is somehow
-automatically filled. This is used to determine which columns to empty
+Set this to a true value for a column whose value is somehow
+automatically set. This is used to determine which columns to empty
 when cloning objects using C<copy>.
 
 =item is_foreign_key
 
-Set this to a true value if this column represents a key from a
+Set this to a true value for a column that contains a key from a
 foreign table. This is currently not used by DBIx::Class.
 
 =item default_value
 
-Set this to the default value which will be inserted into this column
-by the database. Can contain either values or functions. This is
+Set this to the default value which will be inserted into a column
+by the database. Can contain either a value or a function. This is
 currently not used by DBIx::Class.
 
 =item sequence
 
-Sets the name of the sequence to use to generate values.  If not 
-specified, L<DBIx::Class::PK::Auto> will attempt to retrieve the 
-name of the sequence from the database automatically.
+Set this on a primary key column to the name of the sequence used to
+generate a new key value. If not specified, L<DBIx::Class::PK::Auto>
+will attempt to retrieve the name of the sequence from the database
+automatically.
 
 =back
 
@@ -120,7 +120,7 @@ name of the sequence from the database automatically.
 
   $table->add_column('col' => \%info?);
 
-Convenience alias to add_columns
+Convenience alias to add_columns.
 
 =cut
 
@@ -147,7 +147,7 @@ sub add_columns {
 
   if ($obj->has_column($col)) { ... }
 
-Returns 1 if the source has a column of this name, 0 otherwise.
+Returns true if the source has a column of this name, false otherwise.
 
 =cut
 
@@ -193,7 +193,7 @@ sub column_info {
 
   my @column_names = $obj->columns;
 
-Returns all column names in the order they were declared to add_columns
+Returns all column names in the order they were declared to add_columns.
 
 =cut
 
@@ -209,7 +209,7 @@ sub columns {
 
 =over 4
 
-=item Arguments: (@cols)
+=item Arguments: @cols
 
 =back
 
@@ -249,9 +249,8 @@ sub primary_columns {
 
 Declare a unique constraint on this source. Call once for each unique
 constraint. Unique constraints are used when you call C<find> on a
-L<DBIx::Class::ResultSet>, only columns in the constraint are searched,
-
-e.g.,
+L<DBIx::Class::ResultSet>. Only columns in the constraint are searched,
+for example:
 
   # For UNIQUE (column1, column2)
   __PACKAGE__->add_unique_constraint(
@@ -286,8 +285,8 @@ sub unique_constraints {
 =head2 from
 
 Returns an expression of the source to be supplied to storage to specify
-retrieval from this source; in the case of a database the required FROM clause
-contents.
+retrieval from this source. In the case of a database, the required FROM
+clause contents.
 
 =cut
 
@@ -314,7 +313,7 @@ the current schema. For example:
     'foreign.book_id' => 'self.id',
   });
 
-The condition C<$cond> needs to be an SQL::Abstract-style
+The condition C<$cond> needs to be an L<SQL::Abstract>-style
 representation of the join between the tables. For example, if you're
 creating a rel from Author to Book,
 
@@ -348,8 +347,8 @@ the main class. If, for example, you do the following:
 Then, assuming LinerNotes has an accessor named notes, you can do:
 
   my $cd = CD->find(1);
-  $cd->notes('Notes go here'); # set notes -- LinerNotes object is
-                              # created if it doesn't exist
+  # set notes -- LinerNotes object is created if it doesn't exist
+  $cd->notes('Notes go here');
 
 =item accessor
 
@@ -408,7 +407,7 @@ sub add_relationship {
 
 =head2 relationships
 
-Returns all valid relationship names for this source
+Returns all relationship names for this source.
 
 =cut
 
@@ -420,11 +419,12 @@ sub relationships {
 
 =over 4
 
-=item Arguments: ($relname)
+=item Arguments: $relname
 
 =back
 
-Returns the relationship information for the specified relationship name
+Returns a hash of relationship information for the specified relationship
+name.
 
 =cut
 
@@ -437,11 +437,11 @@ sub relationship_info {
 
 =over 4
 
-=item Arguments: ($rel)
+=item Arguments: $rel
 
 =back
 
-Returns 1 if the source has a relationship of this name, 0 otherwise.
+Returns true if the source has a relationship of this name, false otherwise.
 
 =cut
 
@@ -454,11 +454,11 @@ sub has_relationship {
 
 =over 4
 
-=item Arguments: ($relation)
+=item Arguments: $relation
 
 =back
 
-Returns the join structure required for the related result source
+Returns the join structure required for the related result source.
 
 =cut
 
@@ -493,7 +493,7 @@ sub resolve_join {
 
 =over 4
 
-=item Arguments: ($cond, $as, $alias|$object)
+=item Arguments: $cond, $as, $alias|$object
 
 =back
 
@@ -536,7 +536,7 @@ sub resolve_condition {
 
 =over 4
 
-=item Arguments: (hashref/arrayref/scalar)
+=item Arguments: hashref/arrayref/scalar
 
 =back
 
@@ -641,11 +641,11 @@ sub resolve_prefetch {
 
 =over 4
 
-=item Arguments: ($relname)
+=item Arguments: $relname
 
 =back
 
-Returns the result source object for the given relationship
+Returns the result source object for the given relationship.
 
 =cut
 
@@ -661,11 +661,11 @@ sub related_source {
 
 =over 4
 
-=item Arguments: ($relname)
+=item Arguments: $relname
 
 =back
 
-Returns the class object for the given relationship
+Returns the class name for objects in the given relationship.
 
 =cut
 
@@ -713,7 +713,7 @@ sub resultset {
 
 =head2 throw_exception
 
-See throw_exception in L<DBIx::Class::Schema>.
+See L<DBIx::Class::Schema/"throw_exception">.
 
 =cut
 
@@ -726,7 +726,6 @@ sub throw_exception {
   }
 }
 
-
 =head1 AUTHORS
 
 Matt S. Trout <mst@shadowcatsystems.co.uk>
index 4a40e82..d9a29c8 100644 (file)
@@ -387,7 +387,7 @@ sub is_column_changed {
 
 =over 4
 
-=item Arguments: ($column, $column_info)
+=item Arguments: $column, $column_info
 
 =back
 
index 50d1a39..0b9b969 100644 (file)
@@ -50,7 +50,7 @@ use L<DBIx::Class> and allows you to use more than one concurrent connection
 with your classes.
 
 NB: If you're used to L<Class::DBI> it's worth reading the L</SYNOPSIS>
-carefully as DBIx::Class does things a little differently. Note in
+carefully, as DBIx::Class does things a little differently. Note in
 particular which module inherits off which.
 
 =head1 METHODS
@@ -59,12 +59,12 @@ particular which module inherits off which.
 
 =over 4
 
-=item Arguments: ($moniker, $component_class)
+=item Arguments: $moniker, $component_class
 
 =back
 
 Registers a class which isa L<DBIx::Class::ResultSourceProxy>. Equivalent to
-calling
+calling:
 
   $schema->register_source($moniker, $component_class->result_source_instance);
 
@@ -79,7 +79,7 @@ sub register_class {
 
 =over 4
 
-=item Arguments: ($moniker, $result_source)
+=item Arguments: $moniker, $result_source
 
 =back
 
@@ -105,15 +105,13 @@ sub register_source {
 
 =over 4
 
-=item Arguments: ($moniker)
+=item Arguments: $moniker
 
-=item Returns: $classname
+=item Return Value: $classname
 
 =back
 
-Retrieves the result class name for the given moniker.
-
-e.g.,
+Retrieves the result class name for the given moniker. For example:
 
   my $class = $schema->class('CD');
 
@@ -128,9 +126,9 @@ sub class {
 
 =over 4
 
-=item Arguments: ($moniker)
+=item Arguments: $moniker
 
-=item Returns: $result_source
+=item Return Value: $result_source
 
 =back
 
@@ -156,13 +154,12 @@ sub source {
 
 =over 4
 
-=item Returns: @source_monikers
+=item Return Value: @source_monikers
 
 =back
 
 Returns the source monikers of all source registrations on this schema.
-
-e.g.,
+For example:
 
   my @source_monikers = $schema->sources;
 
@@ -174,9 +171,9 @@ sub sources { return keys %{shift->source_registrations}; }
 
 =over 4
 
-=item Arguments: ($moniker)
+=item Arguments: $moniker
 
-=item Returns: $result_set
+=item Return Value: $result_set
 
 =back
 
@@ -203,11 +200,11 @@ With no arguments, this method uses L<Module::Find> to find all classes under
 the schema's namespace. Otherwise, this method loads the classes you specify
 (using L<use>), and registers them (using L</"register_class">).
 
-It is possible to comment out classes with a leading '#', but note that perl
-will think it's a mistake (trying to use a comment in a qw list) so you'll
-need to add "no warnings 'qw';" before your load_classes call.
+It is possible to comment out classes with a leading C<#>, but note that perl
+will think it's a mistake (trying to use a comment in a qw list), so you'll
+need to add C<no warnings 'qw';> before your load_classes call.
 
-e.g.,
+Example:
 
   My::Schema->load_classes(); # loads My::Schema::CD, My::Schema::Artist,
                               # etc. (anything under the My::Schema namespace)
@@ -286,16 +283,16 @@ sub load_classes {
 
 =over 4
 
-=item Arguments: ($target_namespace, @db_info)
+=item Arguments: $target_namespace, @db_info
 
-=item Returns: $new_schema
+=item Return Value: $new_schema
 
 =back
 
-Calls L<DBIx::Class::schema/"compose_namespace"> to the target namespace,
-calls L<DBIx::Class::Schema/connection>(@db_info) on the new schema, then
-injects the L<DBix::Class::ResultSetProxy> component and a resultset_instance
-classdata entry on all the new classes in order to support
+Calls L<DBIx::Class::Schema/"compose_namespace"> to the target namespace,
+calls L<DBIx::Class::Schema/connection> with @db_info on the new schema,
+then injects the L<DBix::Class::ResultSetProxy> component and a
+resultset_instance classdata entry on all the new classes, in order to support
 $target_namespaces::$class->search(...) method calls.
 
 This is primarily useful when you have a specific need for class method access
@@ -351,7 +348,7 @@ sub compose_connection {
 
 =item Arguments: $target_namespace, $additional_base_class?
 
-=item Returns: $new_schema
+=item Return Value: $new_schema
 
 =back
 
@@ -365,13 +362,13 @@ new $schema object. If C<$additional_base_class> is given, the new composed
 classes will inherit from first the corresponding classe from the current
 schema then the base class.
 
-e.g. (for a schema with My::Schema::CD and My::Schema::Artist classes),
+For example, for a schema with My::Schema::CD and My::Schema::Artist classes,
 
   $schema->compose_namespace('My::DB', 'Base::Class');
   print join (', ', @My::DB::CD::ISA) . "\n";
   print join (', ', @My::DB::Artist::ISA) ."\n";
 
-Will produce the output
+will produce the output
 
   My::Schema::CD, Base::Class
   My::Schema::Artist, Base::Class
@@ -411,7 +408,7 @@ sub compose_namespace {
 
 =over 4
 
-=item Arguments: ($target, @info)
+=item Arguments: $target, @info
 
 =back
 
@@ -431,9 +428,9 @@ sub setup_connection_class {
 
 =over 4
 
-=item Arguments: (@args)
+=item Arguments: @args
 
-=item Returns: $new_schema
+=item Return Value: $new_schema
 
 =back
 
@@ -464,9 +461,9 @@ sub connection {
 
 =over 4
 
-=item Arguments: (@info)
+=item Arguments: @info
 
-=item Returns: $new_schema
+=item Return Value: $new_schema
 
 =back
 
@@ -512,9 +509,9 @@ sub txn_rollback { shift->storage->txn_rollback }
 
 =over 4
 
-=item Arguments: (C<$coderef>, @coderef_args?)
+=item Arguments: C<$coderef>, @coderef_args?
 
-=item Returns: The return value of $coderef
+=item Return Value: The return value of $coderef
 
 =back
 
@@ -619,7 +616,7 @@ sub txn_do {
 
 =over 4
 
-=item Returns: $new_schema
+=item Return Value: $new_schema
 
 =back
 
@@ -643,7 +640,7 @@ sub clone {
 
 =over 4
 
-=item Arguments: ($moniker, \@data);
+=item Arguments: $moniker, \@data;
 
 =back
 
@@ -679,7 +676,7 @@ sub populate {
 
 =over 4
 
-=item Arguments: ($message)
+=item Arguments: $message
 
 =back
 
@@ -697,7 +694,7 @@ sub throw_exception {
 
 =over 4
 
-=item Arguments: ($sqlt_args)
+=item Arguments: $sqlt_args
 
 =back
 
index f0591ae..b3ac604 100644 (file)
@@ -24,7 +24,6 @@ __END__
 =head1 NAME
 
     DBIx::Class::Serialize::Storable - hooks for Storable freeze/thaw
-    (EXPERIMENTAL)
 
 =head1 SYNOPSIS
 
@@ -33,16 +32,14 @@ __END__
     
     # meanwhile, in a nearby piece of code
     my $cd = $schema->resultset('CD')->find(12);
-    $cache->set($cd->ID, $cd); # if the cache uses Storable, this
-                              # will work automatically
+    # if the cache uses Storable, this will work automatically
+    $cache->set($cd->ID, $cd);
 
 =head1 DESCRIPTION
 
 This component adds hooks for Storable so that row objects can be
 serialized. It assumes that your row object class (C<result_class>) is
-the same as your table class, which is the normal situation. However,
-this code is not yet well tested, and so should be considered
-experimental.
+the same as your table class, which is the normal situation.
 
 =head1 AUTHORS
 
index 6d6fff8..9d17a04 100644 (file)
@@ -18,6 +18,7 @@ use base qw/SQL::Abstract::Limit/;
 
 sub select {
   my ($self, $table, $fields, $where, $order, @rest) = @_;
+  $table = $self->_quote($table) unless ref($table);
   @rest = (-1) unless defined $rest[0];
   local $self->{having_bind} = [];
   my ($sql, @ret) = $self->SUPER::select(
@@ -26,6 +27,27 @@ sub select {
   return wantarray ? ($sql, @ret, @{$self->{having_bind}}) : $sql;
 }
 
+sub insert {
+  my $self = shift;
+  my $table = shift;
+  $table = $self->_quote($table) unless ref($table);
+  $self->SUPER::insert($table, @_);
+}
+
+sub update {
+  my $self = shift;
+  my $table = shift;
+  $table = $self->_quote($table) unless ref($table);
+  $self->SUPER::update($table, @_);
+}
+
+sub delete {
+  my $self = shift;
+  my $table = shift;
+  $table = $self->_quote($table) unless ref($table);
+  $self->SUPER::delete($table, @_);
+}
+
 sub _emulate_limit {
   my $self = shift;
   if ($_[3] == -1) {
@@ -90,7 +112,12 @@ sub _table {
   } elsif (ref $from eq 'HASH') {
     return $self->_make_as($from);
   } else {
-    return $from;
+    return $from; # would love to quote here but _table ends up getting called
+                  # twice during an ->select without a limit clause due to
+                  # the way S::A::Limit->select works. should maybe consider
+                  # bypassing this and doing S::A::select($self, ...) in
+                  # our select method above. meantime, quoting shims have
+                  # been added to select/insert/update/delete here
   }
 }
 
@@ -500,7 +527,7 @@ sub _execute {
       $self->debugfh->print("$sql: " . join(', ', @debug_bind) . "\n");
   }
   my $sth = $self->sth($sql,$op);
-  $self->throw_exception("no sth generated via sql: $sql") unless $sth;
+  $self->throw_exception('no sth generated via sql (' . $self->_dbh->errstr . "): $sql") unless $sth;
   @bind = map { ref $_ ? ''.$_ : $_ } @bind; # stringify args
   my $rv;
   if ($sth) {
@@ -660,10 +687,11 @@ sub deployment_statements {
 
 sub deploy {
   my ($self, $schema, $type, $sqltargs) = @_;
-  my @statements = $self->deployment_statements($schema, $type, $sqltargs);
-  foreach(split(";\n", @statements)) {
-    $self->debugfh->print("$_\n") if $self->debug;
-    $self->dbh->do($_) or warn "SQL was:\n $_";
+  foreach my $statement ( $self->deployment_statements($schema, $type, $sqltargs) ) {
+    for ( split(";\n", $statement)) {
+      $self->debugfh->print("$_\n") if $self->debug;
+      $self->dbh->do($_) or warn "SQL was:\n $_";
+    }
   }
 }
 
index 42c253f..2550adc 100644 (file)
@@ -30,8 +30,6 @@ information.
 
 =head2 new
 
-=back
-
 Returns a new L<DBIx::Class::Storage::DBI::Cursor> object.
 
 =cut
@@ -57,9 +55,9 @@ sub new {
 
 =over 4
 
-=item Arguments: (none)
+=item Arguments: none
 
-=item Returns: \@row_columns
+=item Return Value: \@row_columns
 
 =back
 
@@ -99,9 +97,9 @@ sub next {
 
 =over 4
 
-=item Arguments: (none)
+=item Arguments: none
 
-=item Returns: \@row_columns+
+=item Return Value: \@row_columns+
 
 =back
 
@@ -123,8 +121,6 @@ sub all {
 
 =head2 reset
 
-=back
-
 Resets the cursor to the beginning of the L<DBIx::Class::ResultSet>.
 
 =cut
index e24e2eb..d9ba0ce 100644 (file)
@@ -18,7 +18,7 @@ __END__
 
 =head1 NAME
 
-DBIx::Class::UUIDMaker::Win32:::Guidgen - Create uuids using Win32::Guidgen
+DBIx::Class::UUIDMaker::Win32::Guidgen - Create uuids using Win32::Guidgen
 
 =head1 SYNOPSIS
 
index 3d25cac..89df553 100644 (file)
@@ -15,7 +15,7 @@ __END__
 
 =head1 NAME
 
-DBIx::Class::UUIDMaker::Win32API:::GUID - Create uuids using Win32API::GUID
+DBIx::Class::UUIDMaker::Win32API::GUID - Create uuids using Win32API::GUID
 
 =head1 SYNOPSIS
 
index b638fd0..73c0e80 100644 (file)
@@ -77,35 +77,67 @@ sub parse {
         foreach my $rel (@rels)
         {
             my $rel_info = $source->relationship_info($rel);
-            next if(!exists $rel_info->{attrs}{accessor} ||
-                    $rel_info->{attrs}{accessor} eq 'multi');
-            # Going by the accessor type isn't such a good idea (yes, I know
-            # I suggested it). I think the best way to tell if something is a
-            # foreign key constraint is to assume if it doesn't include our
-            # primaries then it is (dumb but it'll do). Ignore any rel cond
-            # that isn't a straight hash, but get both sets of keys in full
-            # so you don't barf on multi-primaries. Oh, and a dog-simple
-            # deploy method to chuck the results of this exercise at a db
-            # for testing is
-            # $schema->storage->dbh->do($_) for split(";\n", $sql);
-            #         -- mst (03:42 local time, please excuse any mistakes)
+
             my $rel_table = $source->related_source($rel)->name;
-            my $cond = (keys (%{$rel_info->{cond}}))[0];
-            my ($refkey) = $cond =~ /^\w+\.(\w+)$/;
-            my ($key) = $rel_info->{cond}->{$cond} =~ /^\w+\.(\w+)$/;
-            if($rel_table && $refkey)
+
+            # Ignore any rel cond that isn't a straight hash
+            next unless ref $rel_info->{cond} eq 'HASH';
+
+            # Get the key information, mapping off the foreign/self markers
+            my @cond = keys(%{$rel_info->{cond}});
+            my @refkeys = map {/^\w+\.(\w+)$/} @cond;
+            my @keys = map {$rel_info->{cond}->{$_} =~ /^\w+\.(\w+)$/} @cond;
+
+            if($rel_table)
             {
-                $table->add_constraint(
-                            type             => 'foreign_key',
-                            name             => "fk_${key}",
-                            fields           => $key,
-                            reference_fields => $refkey,
-                            reference_table  => $rel_table,
-                );
+
+                #Decide if this is a foreign key based on whether the self
+                #items are our primary columns.
+
+                # Make sure every self key is in the primary key list
+                my $found;
+                foreach my $key (@keys) {
+                    $found = 0;
+                    foreach my $prim ($source->primary_columns) {
+                        if ($prim eq $key) {
+                            $found = 1;
+                            last;
+                        }
+                    }
+                    last unless $found;
+                }
+
+                # Make sure every primary key column is in the self keys
+                if ($found) {
+                    foreach my $prim ($source->primary_columns) {
+                        $found = 0;
+                        foreach my $key (@keys) {
+                            if ($prim eq $key) {
+                                $found = 1;
+                                last;
+                            }
+                        }
+                        last unless $found;
+                    }
+                }
+
+                # if $found then the two sets are equal.
+
+                # If the sets are different, then we assume it's a foreign key from
+                # us to another table.
+                if (!$found) {
+                    $table->add_constraint(
+                                type             => 'foreign_key',
+                                name             => "fk_$keys[0]",
+                                fields           => \@keys,
+                                reference_fields => \@refkeys,
+                                reference_table  => $rel_table,
+                    );
+                }
             }
         }
     }
-
+    return 1;
 }
 
 1;
index 1647794..ddc2905 100644 (file)
--- a/t/02pod.t
+++ b/t/02pod.t
@@ -2,6 +2,5 @@ use Test::More;
 
 eval "use Test::Pod 1.14";
 plan skip_all => 'Test::Pod 1.14 required' if $@;
-plan skip_all => 'set TEST_POD to enable this test' unless $ENV{TEST_POD};
 
 all_pod_files_ok();
index 18588c8..7a85075 100644 (file)
@@ -6,7 +6,7 @@ BEGIN {
     eval "use DBD::SQLite";
     plan $@
         ? ( skip_all => 'needs DBD::SQLite for testing' )
-        : ( tests => 6 );
+        : ( tests => 7 );
 }
 
 use lib qw(t/lib);
@@ -52,6 +52,13 @@ $rs = DBICTest::CD->search(
            { join => 'artist' });
 cmp_ok($rs->count,'==', 1,"join quoted with brackets.");
 
+my %data = (
+       name => 'Bill',
+       order => '12'
+);
 
+DBICTest->schema->storage->sql_maker->quote_char('`');
+DBICTest->schema->storage->sql_maker->name_sep('.');
 
+cmp_ok(DBICTest->schema->storage->sql_maker->update('group', \%data), 'eq', 'UPDATE `group` SET `name` = ?, `order` = ?', "quoted table names for UPDATE");
 
diff --git a/t/helperrels/26sqlt.t b/t/helperrels/26sqlt.t
new file mode 100644 (file)
index 0000000..4ea58ec
--- /dev/null
@@ -0,0 +1,147 @@
+use Test::More;
+use lib qw(t/lib);
+use DBICTest;
+use DBICTest::HelperRels;
+
+eval "use SQL::Translator";
+plan skip_all => 'SQL::Translator required' if $@;
+
+my $schema = DBICTest::Schema;
+
+plan tests => 27;
+
+my $translator           =  SQL::Translator->new( 
+    parser_args          => {
+        'DBIx::Schema'   => $schema,
+    },
+    producer_args   => {
+    },
+);
+
+$translator->parser('SQL::Translator::Parser::DBIx::Class');
+$translator->producer('SQLite');
+
+my $output = $translator->translate();
+
+my @constraints = 
+ (
+  {'display' => 'twokeys->cd',
+   'selftable' => 'twokeys', 'foreigntable' => 'cd', 
+   'selfcols'  => ['cd'], 'foreigncols' => ['cdid'], 
+   'needed' => 1, on_delete => '', on_update => ''},
+  {'display' => 'twokeys->artist',
+   'selftable' => 'twokeys', 'foreigntable' => 'artist', 
+   'selfcols'  => ['artist'], 'foreigncols' => ['artistid'],
+   'needed' => 1, on_delete => '', on_update => ''},
+  {'display' => 'cd_to_producer->cd',
+   'selftable' => 'cd_to_producer', 'foreigntable' => 'cd', 
+   'selfcols'  => ['cd'], 'foreigncols' => ['cdid'],
+   'needed' => 1, on_delete => '', on_update => ''},
+  {'display' => 'cd_to_producer->producer',
+   'selftable' => 'cd_to_producer', 'foreigntable' => 'producer', 
+   'selfcols'  => ['producer'], 'foreigncols' => ['producerid'],
+   'needed' => 1, on_delete => '', on_update => ''},
+  {'display' => 'self_ref_alias -> self_ref for self_ref',
+   'selftable' => 'self_ref_alias', 'foreigntable' => 'self_ref', 
+   'selfcols'  => ['self_ref'], 'foreigncols' => ['id'],
+   'needed' => 1, on_delete => '', on_update => ''},
+  {'display' => 'self_ref_alias -> self_ref for alias',
+   'selftable' => 'self_ref_alias', 'foreigntable' => 'self_ref', 
+   'selfcols'  => ['alias'], 'foreigncols' => ['id'],
+   'needed' => 1, on_delete => '', on_update => ''},
+  {'display' => 'cd -> artist',
+   'selftable' => 'cd', 'foreigntable' => 'artist', 
+   'selfcols'  => ['artist'], 'foreigncols' => ['artistid'],
+   'needed' => 1, on_delete => '', on_update => ''},
+  {'display' => 'artist_undirected_map -> artist for id1',
+   'selftable' => 'artist_undirected_map', 'foreigntable' => 'artist', 
+   'selfcols'  => ['id1'], 'foreigncols' => ['artistid'],
+   'needed' => 1, on_delete => '', on_update => ''},
+  {'display' => 'artist_undirected_map -> artist for id2',
+   'selftable' => 'artist_undirected_map', 'foreigntable' => 'artist', 
+   'selfcols'  => ['id2'], 'foreigncols' => ['artistid'],
+   'needed' => 1, on_delete => '', on_update => ''},
+  {'display' => 'track->cd',
+   'selftable' => 'track', 'foreigntable' => 'cd', 
+   'selfcols'  => ['cd'], 'foreigncols' => ['cdid'],
+   'needed' => 2, on_delete => '', on_update => ''},
+  {'display' => 'treelike -> treelike for parent',
+   'selftable' => 'treelike', 'foreigntable' => 'treelike', 
+   'selfcols'  => ['parent'], 'foreigncols' => ['id'],
+   'needed' => 1, on_delete => '', on_update => ''},
+  {'display' => 'twokeytreelike -> twokeytreelike for parent1,parent2',
+   'selftable' => 'twokeytreelike', 'foreigntable' => 'twokeytreelike', 
+   'selfcols'  => ['parent1', 'parent2'], 'foreigncols' => ['id1','id2'],
+   'needed' => 1, on_delete => '', on_update => ''},
+  {'display' => 'tags -> cd',
+   'selftable' => 'tags', 'foreigntable' => 'cd', 
+   'selfcols'  => ['cd'], 'foreigncols' => ['cdid'],
+   'needed' => 1, on_delete => '', on_update => ''},
+ );
+
+my $tschema = $translator->schema();
+for my $table ($tschema->get_tables) {
+    my $table_name = $table->name;
+    for my $c ( $table->get_constraints ) {
+        next unless $c->type eq 'FOREIGN KEY';
+
+        ok(check($table_name, scalar $c->fields, 
+              $c->reference_table, scalar $c->reference_fields, 
+              $c->on_delete, $c->on_update), "Constraint on $table_name matches an expected constraint");
+    }
+}
+
+my $i;
+for ($i = 0; $i <= $#constraints; ++$i) {
+ ok(!$constraints[$i]->{'needed'}, "Constraint $constraints[$i]->{display}");
+}
+
+sub check {
+ my ($selftable, $selfcol, $foreigntable, $foreigncol, $ondel, $onupd) = @_;
+
+ $ondel = '' if (!defined($ondel));
+ $onupd = '' if (!defined($onupd));
+
+ my $i;
+ for ($i = 0; $i <= $#constraints; ++$i) {
+     if ($selftable eq $constraints[$i]->{'selftable'} &&
+         $foreigntable eq $constraints[$i]->{'foreigntable'} &&
+         ($ondel eq $constraints[$i]->{on_delete}) &&
+         ($onupd eq $constraints[$i]->{on_update})) {
+         # check columns
+
+         my $found = 0;
+         for (my $j = 0; $j <= $#$selfcol; ++$j) {
+             $found = 0;
+             for (my $k = 0; $k <= $#{$constraints[$i]->{'selfcols'}}; ++$k) {
+                 if ($selfcol->[$j] eq $constraints[$i]->{'selfcols'}->[$k] &&
+                     $foreigncol->[$j] eq $constraints[$i]->{'foreigncols'}->[$k]) {
+                     $found = 1;
+                     last;
+                 }
+             }
+             last unless $found;
+         }
+
+         if ($found) {
+             for (my $j = 0; $j <= $#{$constraints[$i]->{'selfcols'}}; ++$j) {
+                 $found = 0;
+                 for (my $k = 0; $k <= $#$selfcol; ++$k) {
+                     if ($selfcol->[$k] eq $constraints[$i]->{'selfcols'}->[$j] &&
+                         $foreigncol->[$k] eq $constraints[$i]->{'foreigncols'}->[$j]) {
+                         $found = 1;
+                         last;
+                     }
+                 }
+                 last unless $found;
+             }
+         }
+
+         if ($found) {
+             --$constraints[$i]->{needed};
+             return 1;
+         }
+     }
+ }
+ return 0;
+}
index d6dd957..d3f086d 100644 (file)
@@ -28,7 +28,7 @@ __PACKAGE__->load_classes(qw/
     'Producer',
     'CD_to_Producer',
   ),
-  qw/SelfRefAlias TreeLike/
+  qw/SelfRefAlias TreeLike TwoKeyTreeLike/
 );
 
 1;
diff --git a/t/lib/DBICTest/Schema/TwoKeyTreeLike.pm b/t/lib/DBICTest/Schema/TwoKeyTreeLike.pm
new file mode 100644 (file)
index 0000000..9547baf
--- /dev/null
@@ -0,0 +1,22 @@
+package # hide from PAUSE 
+    DBICTest::Schema::TwoKeyTreeLike;
+
+use base qw/DBIx::Class/;
+
+__PACKAGE__->load_components(qw/Core/);
+
+__PACKAGE__->table('twokeytreelike');
+__PACKAGE__->add_columns(
+  'id1' => { data_type => 'integer' },
+  'id2' => { data_type => 'integer' },
+  'parent1' => { data_type => 'integer' },
+  'parent2' => { data_type => 'integer' },
+  'name' => { data_type => 'varchar',
+    size      => 100,
+ },
+);
+__PACKAGE__->set_primary_key(qw/id1 id2/);
+__PACKAGE__->belongs_to('parent', 'TwoKeyTreeLike',
+                          { 'foreign.id1' => 'self.parent1', 'foreign.id2' => 'self.parent2'});
+
+1;