Rose-DB-Object-0.820/000750 000765 000024 00000000000 14065626016 014224 5ustar00johnstaff000000 000000 Rose-DB-Object-0.820/Changes000644 000765 000024 00000204612 14065625405 015532 0ustar00johnstaff000000 000000 0.820 (06.26.2021) - John Siracusa * Added missing semicolon(!) in Rose::DB::Object::Metadata. 0.819 (06.16.2020) - John Siracusa * Attempt to fix a failing test by adding a workaround for an undef return value from crypt(). 0.818 (06.08.2020) - John Siracusa * Fixed tests that were failing due to "future" dates that are now in the past (RT 132782) 0.817 (04.05.2020) - John Siracusa * Added missing module load in Rose::DB::Object::Metadata::Util (RT 132300) 0.816 (04.03.2020) - John Siracusa * Updated to support DBD::Pg 3.8.0 and later. 0.815 (03.17.2015) - John Siracusa * Updated more project URLs. 0.814 (03.17.2015) - John Siracusa * Updated project URLs. 0.813 (11.07.2014) - John Siracusa * Added prepare_options parameter to get_objects_iterator_from_sql() and get_objects_from_sql() Manager methods (RT 98014) 0.812 (11.07.2014) - John Siracusa * Second attempt to address precision and scale mix-ups in auto-loaded numeric column metadata. * Fixed get_objects_count() with custom select lists and distinct (Reported by Alexander Karelas) * Fixed precision and scale references in the tutorial (RT 96079) * Fixed an incorrect method name in the Rose::DB::Object::Helpers documentation (RT 97602) * Fixed a bug where save() parameters were not passed to map record save() calls (RT 98730) * Corrected a typo in the documentation (RT 94100) * Updated tests to work with DBD::Pg versions greater than 2.x.x. 0.811 (03.21.2014) - John Siracusa * Fixed a bug that prevented many-to-many map records from being saved to the database (RT 93531) 0.810 (01.18.2014) - John Siracusa * Improved automated installation detection (RT 92255) 0.809 (12.04.2013) - John Siracusa * Corrected precision and scale for auto-loaded numeric column metadata. (Reported by Justin Hawkins) 0.808 (11.03.2013) - John Siracusa * Fixed typos. 0.807 (08.28.2013) - John Siracusa * Fixed inheritance of Rose::DB::Object::Cached's cached_objects_expire_in attribute. (Patch by Frank Wesemann) 0.806 (06.08.2013) - John Siracusa * Updated iterator leak test to work on perl 5.17.* and later (RT 86000) 0.805 (03.10.2013) - John Siracusa * Changed the recommended Oracle trigger PL/SQL to avoid sequence gaps. (Patch by Tom Adamo.) * Updated several Manager calls in bench.pl, adding the inject_results parameter. 0.804 (02.01.2013) - John Siracusa * Fixed some test failures on perl 5.16.2, mysql 5.5.29, and DBD::Pg 2.19.3. 0.803 (01.04.2013) - John Siracusa * Make char column's parse_value() method honor a column's overflow attribute when handling column values that are too long. * Correct length value in column overflow error messages. 0.802 (01.03.2013) - John Siracusa * Make varchar column's parse_value() method honor a column's overflow attribute when handling column values that are too long. 0.801 (11.24.2012) - John Siracusa * Fixed "DBD::Informix::st execute failed: SQL: -255: Not in transaction" errors in the test suite. (Patch by Sergey Leschenko.) 0.800 (09.09.2012) - John Siracusa * Correct tests to account for the Loader's init_db generation fix in 0.799 behavior (RT 79526) 0.799 (08.10.2012) - John Siracusa * Added column type mappings for varchar2, nvarchar, and nvarchar2. (Suggested by Chris Campise) * Fixed some typos in the Loader documentation (RT 78860) * Fixed a Loader bug that caused the base class's init_db method to be ignored (RT 78571) 0.798 (04.04.2012) - John Siracusa * Fixed a typo in the Rose::DB::Object::Manager documentation. * Fixed a bug that caused save() not to throw an exception when a transaction could not be started (Reported by jdv79) * Converted some uses of each() to keys() to avoid iteration state bugs (RT 75773) * Corrected a broken POD link in the one-to-many relationship documentation. (Reported by Nee) 0.797 (11.21.2011) - John Siracusa * Fixed a bug that prevented explicitly disabling/enabling column triggers from working (RT 72592) 0.796 (10.18.2011) - John Siracusa * Use ENGINE=InnoDB instead of TYPE=InnoDB in MySQL table creation statements to avoid an incompatibility with MySQL 5.5 (RT 71757) 0.795 (07.14.2011) - John Siracusa * The Loader now passes the Manager class name as a second argument to module_preamble and module_postamble subroutines to allow Manager classes to be distinguished from object classes. * Corrected a typo in the ConventionManager documentation ("objs_" should be "_objs") * Fixed a bug that caused load-on-demand columns to be loaded by load(with => ...) method calls. (Reported by Marlon Bailey) 0.794 (12.30.2010) - John Siracusa * Handle null default values for foreign key columns in PostgreSQL (RT 64331) 0.793 (12.21.2010) - John Siracusa * Corrected skip count in t/db-object.t 0.792 (12.20.2010) - John Siracusa * Detect attempts to create methods whose names conflict with methods defined in Rose::DB::Object itself. (Reported by Dave Howorth) 0.791 (10.23.2010) - John Siracusa * Fixed a bug that caused on_save column triggers to fire when loading. * More floating point rounding fixes in the test suite. 0.790 (10.17.2010) - John Siracusa * The auto-initialization process no longer sets column default values to undef when there is no default for the column. Doing this was tripping up the default_exists() method. (Reported by Timo Karhu) * Documented the behavior of the manager_args relationship attribute when a relationship is used as a with_objects or require_objects argument. * Fixed a bug that caused SET columns to be erroneously marked as modified when their accessor methods were called. * Fixed a bug in make_manager_methods() that left base_name undefined. (RT 61963, patch by Chris Malarky) * Improved handling of floating point and string/number conversions in the test suite. 0.789 (06.23.2010) - John Siracusa * Added support for SELECT FOR UPDATE and other forms of locking (Initial patch by Kostas Chatzikokolakis) * Fixed some POD typos (RT 58405) 0.788 (05.22.2010) - John Siracusa * Fixed a bug that prevented function calls like now() from being properly inlined, even when allow_inline_column_values was set to a true value. (Reported by David Bourget) * The Loader/auto-init system will now automatically set the allow_inline_column_values attribute to a true value when a column has a default value that looks like a function call. * Fixed a non-numeric warning with development releases of Math::BigInt. * Fixed SQL reserved word quoting bug. (Reported by Taric Mirza) * Avoid stringifying exception objects when setting error(). (Suggested by Kostas Chatzikokolakis) 0.787 (04.27.2010) - John Siracusa * Added remarks column attribute. (Patch by Adam Mackler) * Improved error message generated by get_objects_from_sql() when an unknown column is encountered. * Added support for Rose::DB's keyword_function_calls attribute. * Added dedicated timestamp with time zone column type. * Inflate triggers now work correctly with lazy-loaded columns. (Reported by Alex Karelas) * Failure to have any valid registered data sources no longer causes a fatal error when looking up default column sequence names during class setup. 0.786 (01.23.2010) - John Siracusa * Improved support for serial columns in Oracle. * Renamed CHECK labels to avoid clashes with the reserved block name. * Added force_lowercase attribute to the Loader and Convention Manager as a means of getting "normal" behavior out of Oracle schemas. * Fixed a bug that caused money columns in PostgreSQL to have column lengths that were too short. * Fixed a POD typo (RT 53272) 0.785 (12.31.2009) - John Siracusa * Fixed Rose::DB::Object::Cached to correctly honor alternate column accessor/mutator method names. (Reported by Kevin McGrath) * Stopped the default auto-init handler from asking the convention manager to name foreign keys that have only partially populated column lists, causing it to burn through the "good" foreign key names. (Patch by Douglas Wilson) * Corrected some typos in the ManyToMany documentation and the tutorial. (Thanks to Bart Dopheide) 0.784 (10.16.2009) - John Siracusa * Fixed a bug introduced by the multi-many Manager bug fix in the 0.783 release. (Reported by Mark Frost) * Added missing exception error explanations to relationship methods. * Updated eval blocks to avoid stomping on $@ from an outer scope. 0.783 (09.14.2009) - John Siracusa * Added new range operators: between, gt_lt, gt_le, ge_lt, and ge_le. * The strip() helper method will now throw an exception when there are pending "on-save" actions. (Reported by Kevin McGrath) * Added strip_on_save_ok parameter to strip() to override the default behavior. * Worked around yet another MySQL empty-string-default "feature." (Reported by Terrence Brannon) * Added missing documentation about the required return value of the "object" handler in the traverse_depth_first() helper method. (Reported by David Christensen) * The traverse_depth_first() helper now preserves the existing context object if a "relationship" handler is not defined. (Reported by David Christensen) * Fixed a bug that prevented scalar reference filter arguments from working correctly with date columns in Manager queries. (Reported by Todd Lyons) * Fixed a multi-many Manager bug that caused duplicate sub-objects to be linked to the wrong parent object. (Reported by Anton Shevchenko) 0.782 (07.09.2009) - John Siracusa * Altered tests to confirm the fix for RT 45836. * Detect enum-like columns in PostgreSQL. (RT 46214) * Added optional warning to the Loader for missing primary keys. (Patch by Ed Loehr) * Fixed a memory leak in the Iterator class. (RT 47294) (Patch by Thomas Whaples) * Unique indexes that have predicates are now skipped by the auto- initialization process. Use the include_predicated_unique_indexes Metadata attribute and/or Loader attribute to override the default. This feature is currently only supported in PostgreSQL. (Patch by Ed Loehr) * Improved unknown method error messages. (Suggested by Brian Miller) * Updated some example code in the documentation. 0.781 (04.19.2009) - John Siracusa * Added an explicit SQL_BLOB bind_param() argument for blob columns in SQLite. (Reported by clausi) * Added manager_iterator_method and support for manager_count_method and manager_delete_method to relevant Relationship classes. (Patch by Peter Karman) * Updated test suite to consider DBD::SQLite 1.19+ non-broken. 0.780 (03.04.2009) - John Siracusa * Fixed a bug that caused the delete_relationships() Metadata method to fail in some circumstances. (Reported by vti) * Fixed a few accessor/mutator mismatches in relationship methods. (Reported by Bharanee) 0.779 (02.26.2009) - John Siracusa * Added missing documentation on one-to-many and many-to-many relationship "iterator" and "find" method type names. * Corrected an error message in Rose::DB::Object::MakeMethods::Generic (RT 43667) 0.778 (02.06.2009) - John Siracusa * Added PostgreSQL's "ltree" query extensions to QueryBuilder. (Patch by Rick Apichairuk) * Improved the efficiency of the check-and-merge function used when related objects are added. (Reported by Bryan Opfer) 0.777 (12.12.2008) - John Siracusa * Fixed a bug that caused foreign key proxy relationships to be clobbered if relationships were set after foreign_keys. (Reported by Peter Karman) 0.776 (12.09.2008) - John Siracusa * Fixed a bug that was preventing the use of new comparison operators with QueryBuilder. (Reported by Derek Wueppelmann) * Added a strict_ops parameter and class method to the Manager to allow the policy to b changed per-call or per-class. * Auto-initialization under Oracle should no longer be confused by tables with the same names in different schemas. (Patch by Benjamin Hitz) * Removed the on-again, off-again restriction on aliasing primary key columns. I think it's off for good this time. * Refined column alias policy to keep from appearing to override more granular column method name customization. * Overhauled related object mutator methods to better match the documentation and reduce unnecessary queries. * Fixed a bug that caused the init_with_tree() helper method to overwrite columns involved in relationship mapping in some circumstances. (Reported by Todd Lyons) 0.775 (11.02.2008) - John Siracusa * The insert_or_update(), load_or_insert(), and load_or_save() helper methods no longer throw an exception when called on an object with no uniquely identifying column(s). (Suggested by Richard Jones) * Added detection of minimum JSON version (2.00) to test suite and the Helpers module. 0.774 (10.25.2008) - John Siracusa * Setting objects related through a one-to-many relationship now does more work to ensure success. (Reported by vti and David Bourget) * Fixed an ON DUPLICATE KEY UPDATE test to work around a MySQL "strict mode" quirk (or, IMO, "bug") which causes it to complain about a situation that is only a concern if it decided to do an INSERT rather than an UPDATE. (Reported by Richard Jones) * Fixed a regression in t/spot-check-10.t: adding objects to one-to-many-related lists failed in some circumstances. * Updated auto-initialization examples in the synopsis. 0.773 (10.02.2008) - John Siracusa * Added support for multiple add_on_save calls prior to save(), which is how everyone expected it to behave anyway, and how the documentation always seemed to imply that it did work. Well, now it does. * Pre-saved objects now get their foreign key columns hooked up correctly when passed as arguments to add_on_save methods. (Reported by George Hartzell) * ...-to-many related object lists will now be re-fetched on demand after an add_on_save and a subsequent save(). (Reported by George Hartzell) 0.7722 (09.29.2008) - John Siracusa * Fixed compatibility with older versions of ExtUtils::MakeMaker. 0.7721 (09.29.2008) - John Siracusa * Updated distribution metadata for ExtUtils::MakeMaker 6.44. * Added a column type mapping for MySQL's MEDIUMINT type. (Reported by Andreas Dewes) 0.772 (09.26.2008) - John Siracusa * Altered Rose::DB::Object::Cached to override insert() and update methods. (Suggested by Kevin McGrath) * The update() method now marks updated objects as being in the database (see is_in_db() in Rose::DB::Object::Util) (Suggested by Kevin McGrath) * The insert_or_update() and insert_or_update_on_duplicate_key() helper methods now call save() instead of insert() or update() (passing the appropriate flags to cause an insert or update) in order also save child objects. (Suggested by Kevin McGrath) * The set_column_value_modified() function in Rose::DB::Object::Util now also clears any "db-ready" column value the object may be holding for that column. * Added a dirty_columns() helper method (Suggested by jdv79) 0.7713 (09.16.2008) - John Siracusa * More test skipping fixes. 0.7712 (09.15.2008) - John Siracusa * Improved detection of broken DBD::SQLite versions. 0.7711 (09.15.2008) - John Siracusa * Fixed typos in POD. * Improved test skipping conditions. * Clarified JSON module version requirements. 0.771 (09.12.2008) - John Siracusa * Traversal and recursive serialization helper method added. * Non-persistent columns feature added. * Made "where" an alias for the Manager's "query" parameter. (Requested by Ask Bjørn Hansen) * Documented restrictions on the Manager's "select" parameter. * Fixed bug that prevented CURRENT_TIMESTAMP from being properly inlined in queries sent to SQLite. (RT 37224) * Fixed a memory leak. (Reported by Christopher Laco) * The "cluck" error mode now correctly calls cluck() rather than croak(). (Reported by Kevin McGrath) * Added support for Oracle date/time column keywords. * Cascaded delete now properly cascades to one-to-one related objects. (Reported by kittens) 0.770 (05.28.2008) - John Siracusa * Added "iterator" method type, similar to "find", to OneToMany and ManyToMany. (Patch by Peter Karman - peknet@gmail.com) * Updated the Loader documentation to describe an important consideration when regenerating modules with make_modules(). * Improved error propagation in relationship methods. (Suggested by Wiggins d'Anconia) * Skip the interactive part of the test suite when the AUTOMATED_TESTING environment variable is set. * Test suite now accounts for versions of DBD::mysql that predate the mysql_is_auto_increment column attribute. 0.769 (04.01.2008) - John Siracusa * Improved the default singular/plural conversion rules in the Convention Manager. (Suggested by David Brownlee) * Added new join type override syntax for the Manager's with_objects and require_objects parameters. * Added column method naming conventions to the Convention Manager. * Added Manager naming conventions to the Convention Manager and exposed more Manager-related defaults in the Metadata and Manager class. (Patch by Bradley C Bailey, modified by John Siracusa) * Column method names are now allowed at the end of compound Manager query parameters (e.g., "a.b.method") * Clarified column/method query parameter documentation. * PostgreSQL tests are not skipped when DBD::Pg version 2.1.x or 2.2.0 is installed. (Bus error for me in t/deep-joins.t) 0.768 (02.25.2008) - John Siracusa * Changed mailing list and wiki URLs. * Fixed the "warn" overflow mode for character columns to carp instead of croaking. (Reported by John Ingram) * Refined workaround for http://rt.cpan.org//Ticket/Display.html?id=33193 to apply only to versions that exhibited this bug. * Added the forget_related() helper method to the "all" export tag. 0.767 (02.15.2008) - John Siracusa * Added the forget_related() helper method. * Enhanced and documented the long-dormant "hints" Manager parameter. * Added a work-around for a DBD::Pg 2.0.0 array/bind_col() bug: http://rt.cpan.org//Ticket/Display.html?id=33193 * Improved the column method handling for array-reference values that may be fetched from array columns by DBD::Pg 2.0.0. * Fixed a bug in the test suite that caused some PostgreSQL tests to fail if the "chkpass" column type was not installed. (Reported by Randal Schwartz) 0.7665 (02.08.2008) - John Siracusa * Fixed a bug that prevented the convention manager's auto_table_name() method from honoring the tables_are_singular() attribute value. (Reported by Ben Tilly) * The new, more correct behavior of Rose::DB 0.739's array column value parsing and formatting revealed a bug in QueryBuilder's handling of "(any|all)_in_array" conditions involving empty list arguments. This is now fixed. 0.7664 (02.06.2008) - John Siracusa * Fixed a bug that caused boolean columns to be incorrectly marked as modified. (Reported by Grzegorz Nosek) * Added sql_qualify_column_names_on_load() Metadata method to help support PostgreSQL functions that can masquerade as columns if they're prefixed by the table name. (Suggested by Grzegorz Nosek) 0.7663 (02.04.2008) - John Siracusa * Fixed a bug that caused delete_on_save method creation for foreign keys to fail in some circumstances. (Reported by Justin Ellison) * Fixed a bug that prevented Perl code from being emitted for non-set columns with check_in attributes. (Reported by Sam Tregar) * Pushed cache control methods down into Rose::DB::Object::Cached in preparation for more caching subclasses. * The clear_object_cache() method now correctly clears load timestamps as well. (Patch by Justin Ellison) 0.7662 (01.30.2008) - John Siracusa * Fixed copy-and-paste-o in Rose::DB::Object::Cached code. 0.7661 (01.29.2008) - John Siracusa * Fixed method clash detection in Rose::DB::Object::Manager. * Streamlined caching implementation in Rose::DB::Object::Cached. 0.766 (12.13.2007) - John Siracusa * Added the unique_key_by_name() metadata method. * Added the ability to do unrestricted joins in some circumstances. * Added the remember_all() class method to Rose::DB::Object::Cached. * Added the undef_overrides_default column attribute. * The key_column() method in the ForeignKey class now works correctly. (Patch by Christopher Masto) * Further synced datetime and timestamp method-maker code. * Added a test suite exclusion for DBD::SQLite 1.14, which still suffers from this bug: http://rt.cpan.org/Public/Bug/Display.html?id=21472 * Improved detection of fatal errors during class setup. * Added a "gotchas" section to the Loader documentation. * Fixed propagation of db objects in update and delete Manager methods. * Fixed a bug that caused some cached SQL to persist incorrectly after inheritance. (Patch by Daniel Koch) * Fiddled with not_found() detection. (Changes suggested by Philip Dye) * Made one-to-one relationships (attempt to) work even when uniqueness is not apparent in the metadata. * The Loader no longer chokes on SQLite columns that use the current_timestamp keyword. (Reported by George Hartzell) * Setting undef integer attributes to zero is now correctly detected as a modification. 0.765 (07.21.2007) - John Siracusa * Added a value_type attribute to SET columns. * Added a normalize_get_objects_args() utility method to make custom Manager methods less cumbersome to implement. * Setting a BigInt column to undef no longer sets it to zero. (Reported by Jeffrey Horn) * Corrected error propagation in many-to-many "find" methods when bad arguments are passed. (Reported by Michael Reece) * Added "use strict" the output of perl_manager_class(). * Restored default use of table aliases in Manager queries. The new table_aliases parameter can be used to alter the behavior. * Added support for literal sort_by parameters using scalar references. * Added is/is_not comparison operators to QueryBuilder. (Suggested by Jonathan Vanasco) * Scalar references appearing in the select => ... list in Manager calls are now passed through unmodified. * Existing map records are now correctly checked for when adding items through a many-to-many relationship. (Reported by Drew Taylor) * Using a nonexistent column name in a primary or unique key is now a fatal error. (Reported by Philip Dye) * Multi-columns "select count(distinct ...)" queries now fall back to count(*) on a subselect in databases that do not support calling count on multi-argument distinct clauses. (Reported by Derek Watson) * The auto-init system will now skip PostgreSQL functional indexes when extracting unique keys. (Reported by Jonathan Vanasco) * Fixed a bug that caused inner joins to be used inappropriately in certain cases with many-to-many relationships or when nested joins are disabled. * Fixed a bug that caused the auto-init system to fold multiple foreign keys that reference the same remote key into a single multi-column foreign key. (Reported by Marlon Bailey) * Fixed a Manager bug that caused count queries to use incorrect table aliases when passed empty with_objects or require_objects array reference values. (Reported by Denis Moskowitz) * Fixed a bug that prevented relationship and foreign key names from being resolved when used in nested query parameters. * Relationship count methods no longer die when the count is zero. (Reported by Derek Watson) * Setting enum fields to undef now works correctly. (Reported by Ovid) * Columns with custom DBI bind attributes are now updated correctly. (Reported by Derek Watson) * Epoch columns with zero (0) default values now work correctly. (Reported by Peter Karman) * Setting boolean columns to null (undef) now works correctly. (Reported by Derek Watson) * Fixed a bug that caused literal query parameters with bind arguments to become corrupted after their first use. * Changed the way classes are registered in order to fix a Loader bug that caused cross-database foreign keys to be erroneously created when tables with the same names exists in two different databases. (Reported by Adrian Howard) * Deleting one-to-one related objects on save now works correctly. (Reported by Ovid) * The "find" method for many-to-many relationships now propagates custom Manager arguments correctly. (Patch by Michael Reece) * The use_key parameter to load() now dies if an invalid key is passed. (Reported by Jonathan Vanasco) 0.764 (05.04.2007) - John Siracusa * Added the strip() helper method. * Added a "find" method type to many-to-many relationships. * Added a "count" method type to ...-to-many relationships. * Added support for nested joins. * The setup() method now supports a "helpers" shortcut for importing methods from Rose::DB::Object::Helpers. * Added the dubious require_primary_key parameter and object attribute to the Loader. (Requested by Teodor Zlatanov) * Added two syntaxes for literal SQL to QueryBuilder. * Added the with_column_triggers attribute to foreign keys and singular relationships in order to keep columns and related objects in sync. * Fixed a bug that caused some optional related objects to be improperly transformed into required objects. (Reported by Ethan Rowe) * Improved detection of errors when auto-loading related classes. * Duplicate auto-created map record method names are now detected and reported as a fatal error. * Added and documented a return value for add_columns(). * Added module_preamble and module_postamble features to the Loader's make_modules() method. (Patch by David Christensen) * Made changes_only, cascade, and prepare_cached arguments to save() also apply to *_on_save collections. * Added test and prerequisite version for a Rose::DB bug that prevented certain reserved words from being detected as primary key columns in PostgreSQL. (Reported by Fred Cox) * Baseline Oracle support added to the Loader. (Patch by Teodor Zlatanov) * The clone() and clone_and_reset() methods now handle missing or differently named accessor/mutator methods. * QueryBuilder now supports eq/ne undef for is/is not null comparisons. * Foreign key columns that are also primary key columns are no longer set to undef when a foreign object is set to undef. (Reported by Ovid) * Fixed a bug that caused values not to be checked against the list of valid values in SET columns. (Reported by Adrian Howard) * Fixed a bug that caused column (get/)set methods not to return the correct value when an on_set trigger was applied to the column. (Reported by Cory Bennett) * Fixed a bug that caused enum columns to be incorrectly marked as modified in some circumstances. (Reported by Cory Bennett) * Fixed a bug that caused inflate/deflate triggers to fail under some circumstances. (Patch by Cory Bennett) 0.763 (02.24.2007) - John Siracusa * Re-enabled the DBD::SQLite 1.13 work-around in the test suite, which I temporarily disabled to test a 1.14 candidate and then forgot to re-enable before the 0.762 release. 0.762 (02.24.2007) - John Siracusa * Fixed an unparseable version number in MakeMethods::BigNum that was causing CPAN and associated tools to choke. 0.761 (02.23.2007) - John Siracusa * Added the ability to specify a unique key by name in calls to load(). * Added support for query_args and other Manager parameters to one-to-one and one-to-many relationships. * Added a "find" method type to one-to-many relationships for ad-hoc queries. * Added support for Informix's "datetime year to month" column type. * Updated the dbh() method to be a more conventional proxy for ->db->dbh(). * The get_objects() and delete_objects() Manager methods now accept a lone arrayref or hashref argument as a short way to specify the value of the "query" parameter. * Eliminated warning in the BigNum column type when the GMP math library is not installed. * Added a double precision column type and class for PostgreSQL. * Fixed a bug that caused cascaded save() to fail to cascade beyond a set-on-save related object. * Improved reporting of errors in auto-loaded related modules. * Fixed a bug that caused numeric columns to have invalid length restrictions. (Reported by Fred Cox) * Fixed many incorrect skip counts in the test suite when running against PostgreSQL without CHKPASS support. 0.760 (01.16.2007) - John Siracusa * Fixed a mistake in the test suite that caused spurious failures when testing against Pg without the CHKPASS column type installed. (Reported by Randal Schwartz) 0.759 (01.15.2007) - John Siracusa * Oracle support improved significantly. * Added a "state" export tag to Rose::DB::Object::Util. * Fixed a bug that caused the Manager to unconditionally alias selected columns in some situations. * Added the (dubious) ability to set a list of filtered one-to-many items. * Fixed a database handle leak in the iterator class. (Reported by Peter Karman) 0.758 (11.29.2006) - John Siracusa * Added the get_objects_iterator_from_sql() Manager method and an iterator option for the make_manager_method_from_sql() method. (Suggested by George Hartzell) * Turned off unique column aliases by default and added the unique_aliases Manager parameter to turn them back on. * Fixed some circular references that could have caused database connections to leak. (Reported by Bruno Czekay) 0.757 (11.22.2006) - John Siracusa * Added load_or_save() helper method. * Added support for MySQL's SET data type. * Fixed some SET and ARRAY bugs in QueryBuilder. * Fixed a bug that caused QueryBuilder to choke on inflated BigInt columns. (Reported by Jud) * Modified the rules that govern metadata inheritance in order to allow multiple layers of abstract base classes. * The Loader will now pick up custom convention manager classes from the specified base class. 0.756 (10.29.2006) - John Siracusa * Changed the interaction and behavior of the metadata object's foreign_key_name_generator() method and the convention manager's auto_foreign_key_name() method to avoid some name conflict bugs and create a more sensible flow for foreign key naming. (Suggested by Graham Barr) * Added has_modified_children() has_loaded_related() methods to Rose::DB::Object::Util. * Added an init_with_column_value_pairs() helper method. (Requested by Jonathan Vanasco) * Modified child objects are now correctly detected and handled by cascading save(). (Reported by Lucian Dragus) * Fixed a bug that caused save(changes_only => 1, cascade => 1) to fail in cases where a child object set a key column in the parent object. (Reported by Lucian Dragus) * Fixed a bug in the Manager that caused the with_objects parameter to be ignored when the count_only parameter was set. (Reported by Uwe Voelker) * The column_values_as_*() helper methods no longer require the column_value_pairs() helper to also be imported. (Reported by Jonathan Vanasco) * Fixed a bug caused by blank lines in JSON and YAML output. (Patch by Jonathan Vanasco) * Setting a fixed-length character column to undef now works correctly. * Fixed a bug that caused the benchmark suite to fail under SQLite due to a mishandling of the query_is_sql Manager parameter. * Corrected some typos in the documentation. 0.755 (10.20.2006) - John Siracusa * Fixed a bug that could cause ...-to-many accessors with custom sort orders to fail in some situations. * Removed some imported functions to correctly reflect the documented list of reserved method names. (Reported by Uwe Voelker) * Added the allow_empty_lists parameter to the Manager's get_objects() method. (Suggested by Ask Bjørn Hansen) * Fixed bugs in the query hints implementation. 0.754 (10.06.2006) - John Siracusa * Added an optional db argument to the prime_all_caches() and prime_caches() metadata methods. (Suggested by Jonathan Vanasco) * Improved and documented the way that unique keys are selected by the load() method. (Patch by Graham Barr) * Fixed a bug that caused foreign key column lookups to fail in PostgreSQL when using the unicode database encoding. * Invalid dates are now detected in the query portion of Manager calls. * The test suite now refuses to run SQLite tests if the buggy DBD::SQLite version 1.13 is installed. * Fixed some typos in the documentation. 0.753 (09.17.2006) - John Siracusa * Improved the convention manager's plural_to_singular() method. * Added "match", "imatch", and "similar" operators to QueryBuilder. (Patch by Lucian Dragus) * The Loader will now check if a db_class "isa" Rose::DB already before attempting to load it. (Reported by Randal Schwartz) * The auto-initialization system will now correctly connect one-to-one relationships with foreign keys when appropriate. * Fixed a bug that caused the update_objects() and delete_objects() Manager methods to fail to extract the object_class value from the object_class() method. (Patch by Graham Barr) * Fixed a bug that caused "like" and other match operators to be provided with incorrectly formatted arguments when used with fixed-length CHAR columns. (Reported by Ask Bjørn Hansen) 0.752 (09.06.2006) - John Siracusa * The select parameter to the Manager's get_objects() method now accepts tN.* column specifiers. (Suggested by Jonathan Vanasco) * Added auto_relationship_name_*() methods to the convention manager. * Added rudimentary name conflict resolution abilities to the convention manager's auto_*_name() methods. * Altered the meaning of the time column's precision() attribute and added a scale() attribute to take over the previous meaning. * Renamed the interval column's precision() attribute to scale(). * Aliased columns now work correctly with the select parameter to Manager's get_objects() method. * Fixed a bug that caused the get_objects() Manager method to fail to extract the object_class value from the object_class() method. * Fixed a bug that caused Informix datetime column values to have incorrect "largest qualifier" values for values other than "year." * Eliminated an "uninitialized value" warning when running under mod_perl (Patch by Graham Barr) 0.751 (08.29.2006) - John Siracusa * The Loader is now much, much faster when loading many tables. * Added a tables_are_singular() method to the default ConventionManager. * Improved detection of ambiguous columns in QueryBuilder. * Removed a potentially dubious optimization of the with_objects argument to the get_objects() and get_objects_count() methods. * Added support for auto-initialization in setup() calls. * Added prime_caches(), auto_prime_caches(), and prime_all_caches() Metadata methods to help increase shared memory when running under mod_perl and other similar environments. * The Loader's include_tables and exclude_tables attributes now accept references to arrays of table names as well as regular expressions. * The Loader's include_tables and exclude_tables attributes are now case-insensitive by default. * Fixed a bug that caused save() with sub-objects to fail in some cases. (Reported by Wiggins d'Anconia) * Added examples of the add_on_save relationship methods to the tutorial. 0.75 (08.10.2006) - John Siracusa * Added a cascade option to save(). * Added auto-detection of one-to-one relationships to the Loader. * The object_class parameter to Manager methods now defaults to the return value of the object_class() class method. * The soft() and referential_integrity() methods of the ManyToOne and OneToOne relationship classes have been renamed to optional() and required(), respectively. The old method names still work, but may be removed at some later date. Also, the default values are now determined by a new set of rules, rather than a constant. * Passing invalid query parameters to Manager methods will now cause a fatal error. * Scalar references now work correctly when used in IN(...) queries built by QueryBuilder. (Patch by Perrin Harkins) * Fixed a bug that caused get_set_on_save methods to fail for certain kinds of ...-to-one relationships. * Ignore empty "and" and "or" query parameters in QueryBuilder. (Suggested by Jonathan Vanasco) * Fixed a bug that caused update() to fail for tables where all columns are part of the primary key. (Reported by Danial Pearce) * Minor tweaks to the subselect-based limit/offset code. 0.742 (07.21.2006) - John Siracusa * Added support for a more efficient subselect-based limit/offset implementation for queries that would otherwise have to be scanned through manually. * Added a column class for PostgreSQL's BYTEA column type. * Added support for DBI bind_param() attributes for all column types. * Fixed a bug in the test suite that could cause the TIME(9) column type test to fail in PostgreSQL. * Fixed some POD formatting errors. 0.741 (07.14.2006) - John Siracusa * Added end_of_month_mode parameter to the interval column class. * Prevented perl_* code generation methods from attempting to auto-initialize missing metadata. * Added name/value pair, JSON, and YAML helper methods. * Tweaked the JOIN syntax for MySQL in order to better accommodate MySQL version 5.0.12+. (Reported by Glenn Gallien) * Some small POD corrections. 0.74 (06.30.2006) - John Siracusa * Added the Time column type. * Added support for the changes_only parameter to the insert() method. * Documented the manager_base_class(es) Loader methods. * Fixed a bug that caused the Loader to refuse to use empty or undefined class_prefix values. * Fixed a bug that prevented auto_* methods names from working in calls to the setup() method. * Removed redundant @ISA declaration from generated Manager classes. 0.731 (06.12.2006) - John Siracusa * Fixed a database handle leak in the get_objects_iterator() method. (Reported by Martin Rubli) * Documented the changes_only parameter to the save() and update() methods. (This feature was actually implemented in version 0.73, but I forgot to document it.) * The class_for() metadata method may now be called as a class method, with some caveats. 0.73 (06.07.2006) - John Siracusa * The new setup() method is now the officially recommended way to set up class metadata. * Updated the documentation and the generated Perl code to use the new setup() method. * Related classes are now loaded automatically by default. Added the auto_load_related_classes metadata attribute to control this behavior. * Added the pk_columns() alias for the primary_key_columns() method. * Added insert_or_update() and insert_or_update_on_duplicate_key() helper methods. (Suggested by Guillermo Roditi) * The Loader now automatically skips tables without primary keys. * Moved some database introspection code to a new version of Rose::DB, which this version of Rose::DB::Object now requires. * Non-null character columns are now detected correctly in Informix. * Fixed many bugs related to explicit and auto-detected column defaults. * Corrected the return value of Rose::DB::Object::Cached's load() method to match that of the standard load(). (Reported by Randal Schwartz) * Fixed a bug that caused chkpass columns to be erased after some save()s. (Reported by Cees Hek) * Added an option to use InnoDB with MySQL in the benchmark suite. 0.727 (05.24.2006) - John Siracusa * Fixed a bug that caused custom primary key sequences to be improperly configured in the code generated by the perl_class_defintion() method. (Reported by Ethan Rowe) * Fixed a bug that caused false boolean values to be ignored when an object was loaded, modified, and then saved. (Patch by Cees Hek) 0.726 (05.17.2006) - John Siracusa * Fixed incorrect skip count in t/spot-check-07.t 0.725 (05.17.2006) - John Siracusa * Fixed a bug that caused self-referential many-to-many relationships to fail during cascaded save() operations. (Reported by Michael Drozdov) * The test suite now requires DBD::SQLite version 1.11 or later. * Modified auto-init system to account for custom FetchHashKeyName DBI settings. 0.724 (05.11.2006) - John Siracusa * Added the replace_column() Metadata method. * The add_now and add_on_save relationship methods now return the number of items added when called in scalar context and the list of items added when called in list context. (Suggested by Jesse Brown) 0.723 (05.07.2006) - John Siracusa * Fixed a bug that caused Rose::DB::Object::Cached objects to stay in the cache after being delete()d. * Added clone() and clone_and_reset() Helper methods. * The tutorial now recommends "use base ..." over direct modification of @ISA in order to work better with circular relationships. * Generated Perl code now uses "use base ..." for the same reason. * Simplified object destruction, delegating all database object clean-up to Rose::DB. 0.722 (04.27.2006) - John Siracusa * Really remove Clone::PP this time... 0.721 (04.27.2006) - John Siracusa * Improved and documented metadata inheritance behavior. 0.72 (04.19.2006) - John Siracusa * Many reference-count bugs fixed, including several that could cause database handles to leak. * Fixed a bug that could cause "empty" sub-objects to be created when processing certain with_objects parameters. * Improved the accessor/mutator methods for these column types: set, array, and boolean. * Changed the BigInt and BigSerial column classes to use normal Perl scalars instead of Math::BigInt objects when perl has been compiled to use native 64-bit integers. (Suggested by Jesse Brown) * Added the inject_results Manager parameter to bypass the standard object creation mechanism in cases where it's the dominant factor in overall performance. 0.71 (04.14.2006) - John Siracusa * Correctly clear the "in the database" state flag when a speculative load fails because the object was not found. (Reported indirectly by Svilen Ivanov) * Changed how multi-table queries are formulated for SQLite in order to make the new DBD::SQLite version (1.12) happy. * Fixed errors in the epoch column documentation. * Fixed some internal method-maker bugs. 0.701 (04.05.2006) - John Siracusa * Removed stray "._*" files from module distribution. 0.70 (04.04.2006) - John Siracusa * Added support for the interval data type. * Added support for big integer and serial columns via Math::BigInt. * Added explicit support for columns that store a number of seconds since the Unix epoch (both integer and fractional). * Silenced warning when querying datetime columns with undef values. * Fixed a bug in the loader that prevented auto-generated base class names from being correctly nested under class_prefix. * Fixed a bug that caused duplicate WHERE clauses when a relationship name was the same as the foreign table name. 0.691 (03.16.2006) - John Siracusa * Fixed a bug that prevented the Manager from correctly handling many-to-one relationships in some situations. (Reported by Michael Lackhoff) 0.69 (03.12.2006) - John Siracusa * Added Rose::DB::Object::MixIn and Rose::DB::Object::Helpers. * Made insert() and update() part of Rose::DB::Object's public API. * Fixed typos in some auto-initialization error messages. * Improved checks for MySQL transaction support in the test suite. 0.681 (02.16.2006) - John Siracusa * Removed debugging trigger accidentally left in the 0.68 release. 0.68 (02.16.2006) - John Siracusa * Added support for literal query values using scalar references. * Added translation of undef to the inline keyword NULL in multi-value query operations. (Reported by Teodor Zlatanov) * Added the default_load_speculative() metadata attribute. (Suggested by Teodor Zlatanov) * Added a referential_integrity() attribute to foreign keys and ?-to-one relationships. (Suggested by Teodor Zlatanov) * Corrected some errors in the documentation and added more details on the auto-initialization of relationships. 0.67 (02.07.2006) - John Siracusa * Added the ability to directly set attributes in map records when saving the parent object. (Suggested by Bruno Czekay) * Slightly rearranged and added to the tutorial. 0.66 (02.02.2006) - John Siracusa * Disabled implicit primary table sort clause in the Manager when only fetching rows from a single table. * Allow multiple pre_init_hook()s in the loader and metadata object. * Improved auto-initialization of many-to-many relationships. * Added with_relationships, with_foreign_keys, with_unique_keys, db, and db_class parameters to make_classes() and the loader object. * Renamed some obscure convention manager methods and altered the default behavior of the is_map_class() method slightly. 0.65 (01.27.2006) - John Siracusa * More MySQL 5 bitfield fixes. (Reported by Svilen Ivanov) Important note: if you are using MySQL 5.0.3 or later and you have one or more BIT columns in a given table, you MUST call meta->allow_inline_column_values(1) when setting up the Rose::DB::Object-derived class that fronts the table. * Added missing "use ..." statements to the code generated by perl_class_definition(). * Fixed a bug that prevented certain kinds of self-referential relationships from being initialized properly. (Reported by Bruno Czekay) 0.64 (01.19.2006) - John Siracusa * Worked around yet more SQLite ORDER BY bugs. * Improved the column trigger implementation. * Added support for MySQL 5's brain-dead new BIT column type. Classes must allow_inline_column_values(1) to use it due to the inability of DBD::mysql to bind BIT values correctly without explicit calls to DBI's bind_param() method. * Use alternate strategies for extracting foreign key information from MySQL 5 because the old way no longer works reliably. 0.63 (01.15.2006) - John Siracusa * Made the with_map_records Manager argument work in the manager_args list for relationships. * Added the generate_manager_class_name() method to the loader. * Added the auto_manager_class_name() method to convention manager, and made the loader delegate to it. 0.62 (01.06.2006) - John Siracusa * Fixed a make_modules() bug that caused incorrect init_db() methods to be created in some cases. (Reported by Sean Davis) 0.61 (01.05.2006) - John Siracusa * Added "select" parameter to the Manager which accetps an explicit list of columns to be selected. * It's now possible to filter based on columns that are not selected as part of a Manager query. * Extra parameters can now be passed to the method created by make_manager_method_from_sql() * Fixed several bitfield column accessor method bugs. (Reported by Svilen Ivanov) * The loader's make_modules() method now correctly creates *.pm files for auto-generated base classes. (Reported by Sean Davis) 0.601 (01.01.2006) - John Siracusa * Fixed some incorrect deep join tests. * Reduced the number of PostgreSQL database connections used in the test suite in order to avoid hitting PostgreSQL's default connection limit. * Fixed the DBI benchmark tests broken by the last release. * Uncommented some sections of the benchmark script that were accidentally left commented-out in the last release. 0.60 (12.31.2005) - John Siracusa * Added arbitrary-depth auto-joins to the Manager using a "dot-chained" syntax (e.g., vendor.regions.code) * Added make_modules() method to the loader. * Added pre_init_hook() method to metadata objects and the loader. * Added overflow attribute to control the behavior when a scalar, character, or varchar column value is too long. Possible values are "fatal" (the default), "truncate", and "warn". * Serial columns are now detected correctly even when DBI returns a column type of integer or bigint. (Reported by Cees Hek) * Added support for bigserial columns. * Enum columns now have their list of valid values printed correctly by the perl_hash_definition() method. (Reported by Juan Camacho) * Fixed a bug that caused the loader to trip over mixed-case unique keys in MySQL databases. (Reported by Juan Camacho) * Force MDY dates in PostgreSQL in the test suite, just in case the user has European dates configured. (Reported by Cees Hek) * Fixed numerous PostgreSQL 7.x bugs. (Reported by Cees Hek) * Modified the benchmark suite to further confine each module to its own private set of database rows in order to eliminate the influence of run order during the tests. * Worked around SQLite ORDER BY bugs in order to make the test suite function correctly. 0.59 (12.19.2005) - John Siracusa * Added in_array, any_in_array, and all_in_array query operators for filtering on PostgreSQL array columns. * Fixed a bug that caused certain method overrides to fail when using a custom metadata class with the auto-initialize feature. (Reported by Svilen Ivanov) 0.58 (12.16.2005) - John Siracusa * Added auto-detection of multiple sequence-based primary key columns and primary keys with one or more non-sequence- based columns. * Added support for schema and catalog overrides. * Added tests for db migration with forced and default schemas, multiple sequence-based primary key columns, and primary keys with one or more non-sequence-based columns, migrating to and from databases with and without sequence support. (Whew!) * Added a summary of the default conventions to the Rose::DB::Object::ConventionManager documentation. 0.57 (12.04.2005) - John Siracusa * Fixed broken custom convention manager support in the loader. 0.56 (12.03.2005) - John Siracusa * Updated required Rose::DB version. The 0.55 release was incorrect. 0.55 (12.03.2005) - John Siracusa * Added get_objects_from_sql() and make_manager_method_from_sql() Manager methods. * Made the use of prepare_cached() optional everywhere. It's on by default in Rose::DB::Object, but off by default in Manager. Class data determines the defaults. * Added enum column type. 0.54 (11.30.2005) - John Siracusa * Added SQLite support. * Improved auto-detection of primary key sequence names. * Made primary key sequence names configurable. * Added the "with_map_records" Manager parameter used to fetch map records when auto-joining through a "many to many" relationship. * Fixed a bug in the MySQL foreign key auto-init system. (Reported by Bernhard Graf) * Fixed a bug in the column type class customization system that caused it to fail when combined with auto-initialization. (Reported by Bernhard Graf) * DBI 1.40 or later is now required. 0.53 (11.22.2005) - John Siracusa * Improved handling of table and column names that use reserved words. 0.52 (11.21.2005) - John Siracusa * Fixed bugs in loader when using a DSN instead of a db. 0.51 (11.20.2005) - John Siracusa * Added auto-initialization of relationships. * Added loader. 0.50 (11.17.2005) - John Siracusa * Added optional lazy-loading of column values. * Long-overdue version number bump. 0.081 (11.15.2005) - John Siracusa * Tutorial added. 0.080 (11.14.2005) - John Siracusa * Added column triggers for get, set, load, save, inflate, and deflate. * Added support for new argument types to relationship methods. * Added a named, configurable map for convention manager classes. * Added perl_manager_class() and make_manager_class() meta methods. * Many bug fixes to datetime column methods, cross-database migration, and the auto-init system. 0.079 (10.25.2005) - John Siracusa * Fixed bugs that caused auto-inited many-to-many relationships to be inadequately fleshed-out under some circumstances. 0.078 (10.24.2005) - John Siracusa * Added page and per_page manager parameters for the truly lazy. * Fixed a bug caused by a conflict between the convention manager and the legacy foreign key name generator. 0.077 (10.20.2005) - John Siracusa * Added convention manager. * Added "distinct" and "fetch_only" manager parameters. * Added support for foreign key and relationship names as column prefixes in manager query parameters and sort_by arguments. * Changed manager and query builder to default unprefixed ambiguous columns to belong to the primary table ("t1"). * Fixed a bug that caused make_methods() to fail for "... to one" relationships that have no corresponding foreign key. * Fixed a bug in QueryBuilder that prevented the ability to check for null columns. * Added the ability to query columns that are not SELECTed. 0.076 (10.05.2005) - John Siracusa * Fixed a bug that caused incorrect counts in get_objects_count() when using the require_object parameter with "... to many" relationships. * Added bulk update and delete methods to Manager. * Added cascaded delete, plus a plea in the documentation for users to do this in the database instead. * Added "many to one" relationship and made it the new default relationship type for foreign keys. * Added *_now and *_on_save method types for foreign keys and "... to one" relationships. * Made get_set_on_save and delete_on_save the default auto method types for foreign keys and "... to one" relationships. * load() now returns the object itself on success, which allows for the convenient $obj = MyObject->new(id => 123)->load; * save() now returns the object itself on success, which allows for the convenient $obj = MyObject->new(id => 123)->save; 0.075 (09.19.2005) - John Siracusa * Added support for "many to many" relationships to Manager's "with_objects" and "require_objects" parameters. * Corrected a bug that could cause missing sub-objects when using the Manager's "with_objects" and "require_objects" parameters. * Added warning for doing multiple LEFT OUTER JOINs that could cause a geometric explosion of redundant rows. * Added the "multi_many_ok" parameter to Manager to suppress the warning described above. * Forced inner joins when fetching foreign keys that have key columns that are all NOT NULL. * Added a few more sanity checks to class setup which, in turn, helped me to find and fix a few bugs in the test suite. 0.074 (09.15.2005) - John Siracusa * Repurposed the "with_objects" Manager parameter to do explicit LEFT OUTER JOINs instead of implicit inner joins. * Added "require_objects" Manager parameter to fill the old role of the "with_objects" parameter. * Documented new restrictions associated with the more sensible "with_objects" and "require_objects" parameters. * Added *_sql comparison operators to QueryBuilder, allowing inline SQL. (Requested by Uwe Voelker.) * Removed restriction on aliasing primary key columns. * Fixed bug in Manager that caused fetched objects to be inserted instead of updated on save(). (Reported by Uwe Voelker.) * Corrected a misspelled method name in Rose::DB::Object::Cached. (Reported by David Glass) * Updated benchmark suite to include "one to many" search tests. 0.073 (09.09.2005) - John Siracusa * Added deferred method creation for relationships and foreign keys in order to make "many to many" relationship setup more developer-friendly. * Renamed default auto-method type for foreign keys and relationships from "get" to "get_set", since technically these foreign objects can be set. They just don't go into the database at that point. I'll probably use a different term for that process (e.g., "add") * Some POD and test fixes. 0.0721 (09.08.2005) - John Siracusa * Fixed incorrect method names in Numeric column type. * Added tests to prevent the above error in the future. 0.072 (08.31.2005) - John Siracusa * Added "get" and "set" interfaces to all default column method makers. * More POD fixes. 0.071 (08.30.2005) - John Siracusa * POD fixes. 0.07 (08.30.2005) - John Siracusa * Added rudimentary "many to many" relationship support. * Restructured method-making system to support extensible method types for each kind of thing that makes methods. * Added "get" and "set" interfaces to the scalar method maker. 0.0692 (08.20.2005) - John Siracusa * POD fixes. 0.069 (08.20.2005) - John Siracusa * Whoops, fixed manifest to repair a botched 0.068 release. 0.068 (08.19.2005) - John Siracusa * Enabled multiple joins with the same table in the manager, provided that each occurrence of multi-joined table has the same Rose::DB::Object class fronting it. * Added support for the "alias" parameter to hash-style column definitions. * Added "relationship" objects. * Fixed a bug that caused nearly everything to fail if a column_name_to_method_name_mapper was used. * Moved some class methods out of the object methods section of the documentation, where they shouldn't have been in the first place. 0.0671 (08.15.2005) - John Siracusa * Fixed more MySQL case sensitivity bugs. (Thanks Uwe!) * Changed objects_by_key query_args parameter to be an array ref instead of a hash ref to match the manager change from long ago. * Updated makemethods.t to use MySQL if PostgreSQL is not available. 0.067 (08.14.2005) - John Siracusa * Fixed more MySQL bugs related to foreign keys, timestamps, and boolean support in MySQL 4.0.x. 0.066 (08.12.2005) - John Siracusa * Fixed MySQL case-sensitivity bug. * Added case-sensitivity tests for PostgreSQL and MySQL. * Added accessor, delete, and search with limit and offset benchmarks. 0.0651 (08.11.2005) - John Siracusa * Moved benchmarks into test directory in an attempt to get search.cpan.org to ignore them. 0.065 (08.11.2005) - John Siracusa * Improved the efficiency of manager classes. * Added benchmark suite. * More POD fixes. 0.064 (08.09.2005) - John Siracusa * Duh, forgot the table name and initialize() call in perl_class_definition(). 0.063 (08.09.2005) - John Siracusa * Added foreign key auto-initialization for Informix. 0.062 (08.08.2005) - John Siracusa * Changed default error mode to "fatal" for manager class too. 0.061 (08.07.2005) - John Siracusa * Added foreign key auto-initialization for MySQL. 0.06 (08.05.2005) - John Siracusa * Added auto-initialization (finally). * Plumped-up unique and primary keys into objects. * Changed default error mode to "fatal" * Made substantial additions to the documentation. 0.052 (07.22.2005) - John Siracusa * Added make_manager_methods() to Rose::DB::Object::Manager. * Corrected Rose::DB::Object::Manager POD. 0.052 (07.22.2005) - John Siracusa * Added make_manager_methods() to Rose::DB::Object::Manager. * Corrected Rose::DB::Object::Manager POD. 0.051 (06.22.2005) - John Siracusa * Added abstracted offset parameter to object manager. 0.05 (06.16.2005) - John Siracusa * Moved object cache management further into the metadata object. * Enabled auto-generated primary key values for Rose::DB::Object objects used with the PostgreSQL database. 0.043 (06.16.2005) - John Siracusa * Added time-based expiration to Rose::DB::Object::Cached. * Added catalog attribute to metadata objects. 0.042 (05.04.2005) - John Siracusa * Added not_null attribute to column metadata objects. * Removed use of prepare_cached() because it apparently(?) can't be used across transactions in Informix. 0.041 (04.22.2005) - John Siracusa * Corrected typo in DatetimeYearToSecond column class. (Still need to add tests for those column types...) * Added skip_first parameter to get_object_iterator(). * Added fix to avoid trying to format or truncate undefined dates in datetime columns. 0.04 (04.01.2005) - John Siracusa * Refactored column value parsing. No, really. 0.031 (03.27.2005) - John Siracusa * Corrected typos and errors in the documentation. 0.03 (03.27.2005) - John Siracusa * Added error_mode methods. 0.023 (03.25.2005) - John Siracusa * Added prepare_*_options (undocumented for now). 0.022 (03.18.2005) - John Siracusa * Fixed POD error that was throwing off search.cpan.org. 0.021 (03.17.2005) - John Siracusa * Added documentation for the "pretty" parameter to build_select() * Added support for column value inlining in build_select() 0.02 (03.17.2005) - John Siracusa * Added support for nested boolean logic in queries. 0.013 (03.11.2005) - John Siracusa * Fixed bug in Rose::DB::Object::Std::Metadata that caused the number of primary key columns to appear to be zero. 0.012 (03.10.2005) - John Siracusa * Add tests for aliased unique key fix and primary key aliasing. Documented constraints on column aliasing. Fixed version numbers in this file. Deleted version 0.011. 0.011 (03.09.2005) - John Siracusa * Well that was quick. Fixed copy and paste error that caused a failure to load using an aliased unique key column. Deleted version 0.01. 0.01 (03.09.2005) - John Siracusa * Initial release. Rose-DB-Object-0.820/MANIFEST000644 000765 000024 00000020210 14065626016 015355 0ustar00johnstaff000000 000000 Changes lib/Rose/DB/Object.pm lib/Rose/DB/Object/Cached.pm lib/Rose/DB/Object/Constants.pm lib/Rose/DB/Object/ConventionManager.pm lib/Rose/DB/Object/ConventionManager/Null.pm lib/Rose/DB/Object/Exception.pm lib/Rose/DB/Object/Helpers.pm lib/Rose/DB/Object/Iterator.pm lib/Rose/DB/Object/Loader.pm lib/Rose/DB/Object/MakeMethods/BigNum.pm lib/Rose/DB/Object/MakeMethods/Date.pm lib/Rose/DB/Object/MakeMethods/Generic.pm lib/Rose/DB/Object/MakeMethods/Pg.pm lib/Rose/DB/Object/MakeMethods/Std.pm lib/Rose/DB/Object/MakeMethods/Time.pm lib/Rose/DB/Object/Manager.pm lib/Rose/DB/Object/Metadata.pm lib/Rose/DB/Object/Metadata/Auto.pm lib/Rose/DB/Object/Metadata/Auto/Generic.pm lib/Rose/DB/Object/Metadata/Auto/Informix.pm lib/Rose/DB/Object/Metadata/Auto/MySQL.pm lib/Rose/DB/Object/Metadata/Auto/Oracle.pm lib/Rose/DB/Object/Metadata/Auto/Pg.pm lib/Rose/DB/Object/Metadata/Auto/SQLite.pm lib/Rose/DB/Object/Metadata/Column.pm lib/Rose/DB/Object/Metadata/Column/Array.pm lib/Rose/DB/Object/Metadata/Column/BigInt.pm lib/Rose/DB/Object/Metadata/Column/BigSerial.pm lib/Rose/DB/Object/Metadata/Column/Bitfield.pm lib/Rose/DB/Object/Metadata/Column/Blob.pm lib/Rose/DB/Object/Metadata/Column/Boolean.pm lib/Rose/DB/Object/Metadata/Column/Pg/Bytea.pm lib/Rose/DB/Object/Metadata/Column/Character.pm lib/Rose/DB/Object/Metadata/Column/Date.pm lib/Rose/DB/Object/Metadata/Column/Datetime.pm lib/Rose/DB/Object/Metadata/Column/DatetimeYearToFraction.pm lib/Rose/DB/Object/Metadata/Column/DatetimeYearToFraction1.pm lib/Rose/DB/Object/Metadata/Column/DatetimeYearToFraction2.pm lib/Rose/DB/Object/Metadata/Column/DatetimeYearToFraction3.pm lib/Rose/DB/Object/Metadata/Column/DatetimeYearToFraction4.pm lib/Rose/DB/Object/Metadata/Column/DatetimeYearToFraction5.pm lib/Rose/DB/Object/Metadata/Column/DatetimeYearToMinute.pm lib/Rose/DB/Object/Metadata/Column/DatetimeYearToMonth.pm lib/Rose/DB/Object/Metadata/Column/DatetimeYearToSecond.pm lib/Rose/DB/Object/Metadata/Column/Decimal.pm lib/Rose/DB/Object/Metadata/Column/DoublePrecision.pm lib/Rose/DB/Object/Metadata/Column/Enum.pm lib/Rose/DB/Object/Metadata/Column/Epoch.pm lib/Rose/DB/Object/Metadata/Column/Epoch/HiRes.pm lib/Rose/DB/Object/Metadata/Column/Float.pm lib/Rose/DB/Object/Metadata/Column/Integer.pm lib/Rose/DB/Object/Metadata/Column/Interval.pm lib/Rose/DB/Object/Metadata/Column/Numeric.pm lib/Rose/DB/Object/Metadata/Column/Pg/Chkpass.pm lib/Rose/DB/Object/Metadata/Column/Scalar.pm lib/Rose/DB/Object/Metadata/Column/Serial.pm lib/Rose/DB/Object/Metadata/Column/Set.pm lib/Rose/DB/Object/Metadata/Column/Text.pm lib/Rose/DB/Object/Metadata/Column/Time.pm lib/Rose/DB/Object/Metadata/Column/Timestamp.pm lib/Rose/DB/Object/Metadata/Column/TimestampWithTimeZone.pm lib/Rose/DB/Object/Metadata/Column/Varchar.pm lib/Rose/DB/Object/Metadata/ColumnList.pm lib/Rose/DB/Object/Metadata/ForeignKey.pm lib/Rose/DB/Object/Metadata/MethodMaker.pm lib/Rose/DB/Object/Metadata/Object.pm lib/Rose/DB/Object/Metadata/PrimaryKey.pm lib/Rose/DB/Object/Metadata/Relationship.pm lib/Rose/DB/Object/Metadata/Relationship/ManyToMany.pm lib/Rose/DB/Object/Metadata/Relationship/ManyToOne.pm lib/Rose/DB/Object/Metadata/Relationship/OneToMany.pm lib/Rose/DB/Object/Metadata/Relationship/OneToOne.pm lib/Rose/DB/Object/Metadata/UniqueKey.pm lib/Rose/DB/Object/Metadata/Util.pm lib/Rose/DB/Object/MixIn.pm lib/Rose/DB/Object/QueryBuilder.pm lib/Rose/DB/Object/Std.pm lib/Rose/DB/Object/Std/Cached.pm lib/Rose/DB/Object/Std/Metadata.pm lib/Rose/DB/Object/Tutorial.pod lib/Rose/DB/Object/Util.pm Makefile.PL MANIFEST t/00-warning.t t/as-tree.t t/auto-setup.t t/benchmarks/bench.pl t/benchmarks/lib/MyTest/CDBI/Base.pm t/benchmarks/lib/MyTest/CDBI/Complex/Category.pm t/benchmarks/lib/MyTest/CDBI/Complex/Code.pm t/benchmarks/lib/MyTest/CDBI/Complex/CodeName.pm t/benchmarks/lib/MyTest/CDBI/Complex/Product.pm t/benchmarks/lib/MyTest/CDBI/Simple/Category.pm t/benchmarks/lib/MyTest/CDBI/Simple/Code.pm t/benchmarks/lib/MyTest/CDBI/Simple/CodeName.pm t/benchmarks/lib/MyTest/CDBI/Simple/Product.pm t/benchmarks/lib/MyTest/CDBI/Sweet/Base.pm t/benchmarks/lib/MyTest/CDBI/Sweet/Complex/Category.pm t/benchmarks/lib/MyTest/CDBI/Sweet/Complex/Code.pm t/benchmarks/lib/MyTest/CDBI/Sweet/Complex/CodeName.pm t/benchmarks/lib/MyTest/CDBI/Sweet/Complex/Product.pm t/benchmarks/lib/MyTest/CDBI/Sweet/Simple/Category.pm t/benchmarks/lib/MyTest/CDBI/Sweet/Simple/Code.pm t/benchmarks/lib/MyTest/CDBI/Sweet/Simple/CodeName.pm t/benchmarks/lib/MyTest/CDBI/Sweet/Simple/Product.pm t/benchmarks/lib/MyTest/DBIC/Schema.pm t/benchmarks/lib/MyTest/DBIC/Schema/Complex/Category.pm t/benchmarks/lib/MyTest/DBIC/Schema/Complex/Code.pm t/benchmarks/lib/MyTest/DBIC/Schema/Complex/CodeName.pm t/benchmarks/lib/MyTest/DBIC/Schema/Complex/Product.pm t/benchmarks/lib/MyTest/DBIC/Schema/Simple/Category.pm t/benchmarks/lib/MyTest/DBIC/Schema/Simple/Code.pm t/benchmarks/lib/MyTest/DBIC/Schema/Simple/CodeName.pm t/benchmarks/lib/MyTest/DBIC/Schema/Simple/Product.pm t/benchmarks/lib/MyTest/RDBO/Complex/Category.pm t/benchmarks/lib/MyTest/RDBO/Complex/Category/Manager.pm t/benchmarks/lib/MyTest/RDBO/Complex/Code.pm t/benchmarks/lib/MyTest/RDBO/Complex/CodeName.pm t/benchmarks/lib/MyTest/RDBO/Complex/Product.pm t/benchmarks/lib/MyTest/RDBO/Complex/Product/Manager.pm t/benchmarks/lib/MyTest/RDBO/Simple/Category.pm t/benchmarks/lib/MyTest/RDBO/Simple/Category/Manager.pm t/benchmarks/lib/MyTest/RDBO/Simple/Code.pm t/benchmarks/lib/MyTest/RDBO/Simple/CodeName.pm t/benchmarks/lib/MyTest/RDBO/Simple/Product.pm t/benchmarks/lib/MyTest/RDBO/Simple/Product/Manager.pm t/bind-param.t t/column-triggers.t t/column-values.t t/db-migration.t t/db-object-auto.t t/db-object-cached.t t/db-object-changes-only-1.t t/db-object-changes-only-2.t t/db-object-convention.t t/db-object-foreign-key-auto.t t/db-object-foreign-key.t t/db-object-helpers.t t/db-object-loader.t t/db-object-loader-2.t t/db-object-loader-3.t t/db-object-loader-4.t t/db-object-loader-5.t t/db-object-loader-6.t t/db-object-loader-7.t t/db-object-loader-8.t t/db-object-loader-9.t t/db-object-manager-bulk-ops.t t/db-object-manager.t t/db-object-mapper.t t/db-object-metadata.t t/db-object-relationship.t t/db-object-relationship-auto.t t/db-object-relationship-auto-2.t t/db-object-std-cached.t t/db-object-std.t t/db-object.t t/deep-joins.t t/inheritance.t t/lazy-columns.t t/leaks.t t/lib/My/DB/Gene/Main.pm t/lib/My/DB/Gene2Unigene.pm t/lib/My/DB/Object.pm t/lib/My/DB/Unigene/Main.pm t/lib/My/DB/Opa.pm t/lib/My/DB/Opa/Object.pm t/makemethods-db.t t/makemethods.t t/make-modules.ext t/make-modules.t t/map-record-name-conflict.pl t/multi-many-the-hard-way.t t/multi-pk-sequences.t t/nested-joins.t t/one-to-many-reset.t t/pk-fk-columns.t t/pod.t t/query-builder.t t/rt-cpan-45836.t t/save-cascade.t t/sandbox/code-gen/generated-perl-test.pl t/sandbox/code-gen/lib/.placeholder t/sandbox/code-gen/make-modules.pl t/sandbox/convention/convention-test-auto.pl t/sandbox/convention/convention-test-loader.pl t/sandbox/convention/convention-test.pl t/sandbox/convention/lib/My/Auto/Color.pm t/sandbox/convention/lib/My/Auto/Price.pm t/sandbox/convention/lib/My/Auto/Product.pm t/sandbox/convention/lib/My/Auto/ProductColors.pm t/sandbox/convention/lib/My/Auto/Vendor.pm t/sandbox/convention/lib/My/Color.pm t/sandbox/convention/lib/My/DB.pm t/sandbox/convention/lib/My/Object.pm t/sandbox/convention/lib/My/Price.pm t/sandbox/convention/lib/My/Product.pm t/sandbox/convention/lib/My/ProductColors.pm t/sandbox/convention/lib/My/Region.pm t/sandbox/convention/lib/My/Vendor.pm t/sandbox/schema-clash/lib/Alpha.pm t/sandbox/schema-clash/lib/Beta.pm t/sandbox/schema-clash/sql/alpha.sql t/sandbox/schema-clash/sql/beta.sql t/sandbox/schema-clash/sql/databases.sql t/sandbox/schema-clash/t/one.t t/sandbox/schema-clash/t/two.t t/sandbox/schema-clash/test.pl t/schema-override.t t/spot-check-01.t t/spot-check-02.t t/spot-check-03.t t/spot-check-04.t t/spot-check-05.t t/spot-check-06.t t/spot-check-07.t t/spot-check-08.t t/spot-check-09.t t/spot-check-10.t t/spot-check-11.t t/spot-check-12.t t/spot-check-13.t t/spot-check-14.t t/test-lib.pl t/undef-overrides-default.t t/unique-key-prefs.t META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) Rose-DB-Object-0.820/t/000750 000765 000024 00000000000 14065626016 014467 5ustar00johnstaff000000 000000 Rose-DB-Object-0.820/META.yml000660 000765 000024 00000002271 14065626016 015502 0ustar00johnstaff000000 000000 --- abstract: 'Extensible, high performance object-relational mapper (ORM).' author: - 'John Siracusa ' build_requires: ExtUtils::MakeMaker: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 7.60, CPAN::Meta::Converter version 2.150010' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Rose-DB-Object no_index: directory: - t - inc requires: Bit::Vector: '0' Clone: '0.29' Cwd: '0' DBI: '1.40' Data::Dumper: '2.121' DateTime: '0' File::Path: '0' File::Spec: '0' List::MoreUtils: '0' Math::BigInt: '1.77' Rose::DB: '0.782' Rose::DateTime::Util: '0.532' Rose::Object: '0.854' Scalar::Util: '0' Test::More: '0' Time::Clock: '1.00' perl: '5.006000' resources: MailingList: http://groups.google.com/group/rose-db-object bugtracker: http://rt.cpan.org/NoAuth/Bugs.html?Dist=Rose-DB-Object homepage: http://rosecode.org license: http://dev.perl.org/licenses/ repository: https://github.com/siracusa/rose/tree/master/modules/Rose-DB-Object version: '0.820' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' Rose-DB-Object-0.820/lib/000750 000765 000024 00000000000 14065626016 014772 5ustar00johnstaff000000 000000 Rose-DB-Object-0.820/Makefile.PL000755 000765 000024 00000004137 13641723516 016215 0ustar00johnstaff000000 000000 require 5.006; use ExtUtils::MakeMaker; my $MM_Version = $ExtUtils::MakeMaker::VERSION; if($MM_Version =~ /_/) # dev version { $MM_Version = eval $MM_Version; die $@ if($@); } WriteMakefile(NAME => 'Rose::DB::Object', ABSTRACT_FROM => 'lib/Rose/DB/Object.pm', VERSION_FROM => 'lib/Rose/DB/Object.pm', ($^O =~ /darwin/i ? (dist => { DIST_CP => 'cp' }) : ()), # Avoid Mac OS X ._* files PREREQ_PM => { 'Cwd' => 0, 'Data::Dumper' => '2.121', 'File::Path' => 0, 'File::Spec' => 0, 'DBI' => '1.40', 'DateTime' => 0, 'Time::Clock' => '1.00', 'Test::More' => 0, 'Bit::Vector' => 0, 'Scalar::Util' => 0, 'List::MoreUtils' => 0, 'Math::BigInt' => '1.77', 'Clone' => '0.29', #'Scalar::Util::Clone' => '0.04', 'Rose::DateTime::Util' => '0.532', 'Rose::Object' => '0.854', 'Rose::DB' => '0.782', }, clean => { FILES => "t/*.db" }, AUTHOR => 'John Siracusa ', ($MM_Version >= 6.48 ? (MIN_PERL_VERSION => '5.6.0') : ()), ($MM_Version >= 6.31 ? (LICENSE => 'perl') : ()), ($MM_Version <= 6.44 ? () : (META_MERGE => { resources => { license => 'http://dev.perl.org/licenses/', homepage => 'http://rosecode.org', bugtracker => 'http://rt.cpan.org/NoAuth/Bugs.html?Dist=Rose-DB-Object', repository => 'https://github.com/siracusa/rose/tree/master/modules/Rose-DB-Object', MailingList => 'http://groups.google.com/group/rose-db-object', }, }))); Rose-DB-Object-0.820/META.json000660 000765 000024 00000003631 14065626016 015653 0ustar00johnstaff000000 000000 { "abstract" : "Extensible, high performance object-relational mapper (ORM).", "author" : [ "John Siracusa " ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 7.60, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Rose-DB-Object", "no_index" : { "directory" : [ "t", "inc" ] }, "prereqs" : { "build" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "runtime" : { "requires" : { "Bit::Vector" : "0", "Clone" : "0.29", "Cwd" : "0", "DBI" : "1.40", "Data::Dumper" : "2.121", "DateTime" : "0", "File::Path" : "0", "File::Spec" : "0", "List::MoreUtils" : "0", "Math::BigInt" : "1.77", "Rose::DB" : "0.782", "Rose::DateTime::Util" : "0.532", "Rose::Object" : "0.854", "Scalar::Util" : "0", "Test::More" : "0", "Time::Clock" : "1.00", "perl" : "5.006000" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "http://rt.cpan.org/NoAuth/Bugs.html?Dist=Rose-DB-Object" }, "homepage" : "http://rosecode.org", "license" : [ "http://dev.perl.org/licenses/" ], "repository" : { "url" : "https://github.com/siracusa/rose/tree/master/modules/Rose-DB-Object" }, "x_MailingList" : "http://groups.google.com/group/rose-db-object" }, "version" : "0.820", "x_serialization_backend" : "JSON::PP version 4.06" } Rose-DB-Object-0.820/lib/Rose/000750 000765 000024 00000000000 14065626016 015702 5ustar00johnstaff000000 000000 Rose-DB-Object-0.820/lib/Rose/DB/000750 000765 000024 00000000000 14065626016 016167 5ustar00johnstaff000000 000000 Rose-DB-Object-0.820/lib/Rose/DB/Object.pm000755 000765 000024 00000240670 14065625416 017757 0ustar00johnstaff000000 000000 package Rose::DB::Object; use strict; use Carp(); use Rose::DB; use Rose::DB::Object::Metadata; use Rose::Object; our @ISA = qw(Rose::Object); use Rose::DB::Object::Manager; use Rose::DB::Object::Constants qw(:all); use Rose::DB::Constants qw(IN_TRANSACTION); use Rose::DB::Object::Exception; use Rose::DB::Object::Util(); our $VERSION = '0.820'; our $Debug = 0; # # Object data # use Rose::Object::MakeMethods::Generic ( 'scalar' => [ 'error', 'not_found' ], 'boolean' => [ #FLAG_DB_IS_PRIVATE, STATE_IN_DB, STATE_LOADING, STATE_SAVING, ], ); # # Class methods # sub meta_class { 'Rose::DB::Object::Metadata' } sub meta { my($self) = shift; if(ref $self) { return $self->{META_ATTR_NAME()} ||= $self->meta_class->for_class(ref $self); } return $Rose::DB::Object::Metadata::Objects{$self} || $self->meta_class->for_class($self); } # # Object methods # sub db { my($self) = shift; if(@_) { #$self->{FLAG_DB_IS_PRIVATE()} = 0; my $new_db = shift; # If potentially migrating across db types, "suck through" the # driver-formatted values using the old db before swapping it # with the new one. if($self->{LOADED_FROM_DRIVER()} && $self->{LOADED_FROM_DRIVER()} ne $new_db->{'driver'}) { foreach my $method ($self->meta->column_accessor_method_names) { # Need to catch return to avoid clever methods that # skip work when called in void context. my $val = $self->$method(); } } $self->{'db'} = $new_db; return $new_db; } return $self->{'db'} ||= $self->_init_db; } sub init_db { Rose::DB->new() } sub _init_db { my($self) = shift; my($db, $error); TRY: { local $@; eval { $db = $self->init_db }; $error = $@; } unless($error) { #$self->{FLAG_DB_IS_PRIVATE()} = 1; return $db; } if(ref $error) { $self->error($error); } else { $self->error("Could not init_db() - $error - " . ($db ? $db->error : '')); } $self->meta->handle_error($self); return undef; } sub dbh { my($self) = shift; my $db = $self->db or return undef; if(my $dbh = $db->dbh(@_)) { return $dbh; } else { $self->error($db->error); $self->meta->handle_error($self); return undef; } } use constant LAZY_LOADED_KEY => Rose::DB::Object::Util::lazy_column_values_loaded_key(); sub load { my($self) = $_[0]; # XXX: Must maintain alias to actual "self" object arg my %args = (self => @_); # faster than @_[1 .. $#_]; my $db = $self->db or return 0; my $dbh = $self->dbh or return 0; my $meta = $self->meta; my $prepare_cached = exists $args{'prepare_cached'} ? $args{'prepare_cached'} : $meta->dbi_prepare_cached; local $self->{STATE_SAVING()} = 1; local $self->{SAVING_FOR_LOAD()} = 1; my(@key_columns, @key_methods, @key_values); my $null_key = 0; my $found_key = 0; if(my $key = delete $args{'use_key'}) { my @uk = grep { $_->name eq $key } $meta->unique_keys; if(@uk == 1) { my $defined = 0; @key_columns = $uk[0]->column_names; @key_methods = map { $meta->column_accessor_method_name($_) } @key_columns; @key_values = map { $defined++ if(defined $_); $_ } map { $self->$_() } @key_methods; unless($defined) { $self->error("Could not load() based on key '$key' - column(s) have undefined values"); $meta->handle_error($self); return undef; } if(@key_values != $defined) { $null_key = 1; } } else { Carp::croak "No unique key named '$key' is defined in ", ref($self) } } else { @key_columns = $meta->primary_key_column_names; @key_methods = $meta->primary_key_column_accessor_names; @key_values = grep { defined } map { $self->$_() } @key_methods; unless(@key_values == @key_columns) { my $alt_columns; # Prefer unique keys where we have defined values for all # key columns, but fall back to the first unique key found # where we have at least one defined value. foreach my $cols ($meta->unique_keys_column_names) { my $defined = 0; @key_columns = @$cols; @key_methods = map { $meta->column_accessor_method_name($_) } @key_columns; @key_values = map { $defined++ if(defined $_); $_ } map { $self->$_() } @key_methods; if($defined == @key_columns) { $found_key = 1; last; } $alt_columns ||= $cols if($defined); } if(!$found_key && $alt_columns) { @key_columns = @$alt_columns; @key_methods = map { $meta->column_accessor_method_name($_) } @key_columns; @key_values = map { $self->$_() } @key_methods; $null_key = 1; $found_key = 1; } unless($found_key) { @key_columns = $meta->primary_key_column_names; my $e = Rose::DB::Object::Exception->new( message => "Cannot load " . ref($self) . " without a primary key (" . join(', ', @key_columns) . ') with ' . (@key_columns > 1 ? 'non-null values in all columns' : 'a non-null value') . ' or another unique key with at least one non-null value.', code => EXCEPTION_CODE_NO_KEY); $self->error($e); $meta->handle_error($self); return 0; } } } my $has_lazy_columns = $args{'nonlazy'} ? 0 : $meta->has_lazy_columns; my $column_names; if($has_lazy_columns) { $column_names = $meta->nonlazy_column_names; $self->{LAZY_LOADED_KEY()} = {}; } else { $column_names = $meta->column_names; } # Coerce for_update boolean alias into lock argument if(delete $args{'for_update'}) { $args{'lock'}{'type'} ||= 'for update'; } # # Handle sub-object load in separate code path # if(my $with = $args{'with'}) { my $mgr_class = $args{'manager_class'} || 'Rose::DB::Object::Manager'; my %query; @query{map { "t1.$_" } @key_columns} = @key_values; my($objects, $error); TRY: { local $@; eval { $objects = $mgr_class->get_objects(object_class => ref $self, db => $db, query => [ %query ], with_objects => $with, multi_many_ok => 1, nonlazy => $args{'nonlazy'}, inject_results => $args{'inject_results'}, lock => $args{'lock'}, (exists $args{'prepare_cached'} ? (prepare_cached => $args{'prepare_cached'}) : ())) or Carp::confess $mgr_class->error; if(@$objects > 1) { die "Found ", @$objects, " objects instead of one"; } }; $error = $@; } if($error) { $self->error(ref $error ? $error : "load(with => ...) - $error"); $meta->handle_error($self); return undef; } if(@$objects > 0) { # Sneaky init by object replacement $self = $_[0] = $objects->[0]; # Init by copying attributes (broken; need to do fks and relationships too) #my $methods = $meta->column_mutator_method_names; #my $object = $objects->[0]; # #local $self->{STATE_LOADING()} = 1; #local $object->{STATE_SAVING()} = 1; # #foreach my $method (@$methods) #{ # $self->$method($object->$method()); #} } else { no warnings; $self->error("No such " . ref($self) . ' where ' . join(', ', @key_columns) . ' = ' . join(', ', @key_values)); $self->{'not_found'} = 1; $self->{STATE_IN_DB()} = 0; my $speculative = exists $args{'speculative'} ? $args{'speculative'} : $meta->default_load_speculative; unless($speculative) { $meta->handle_error($self); } return 0; } $self->{STATE_IN_DB()} = 1; $self->{LOADED_FROM_DRIVER()} = $db->{'driver'}; $self->{MODIFIED_COLUMNS()} = {}; return $self || 1; } # # Handle normal load # my($loaded_ok, $error); $self->{'not_found'} = 0; TRY: { local $@; eval { local $self->{STATE_LOADING()} = 1; local $dbh->{'RaiseError'} = 1; my($sql, $sth); if($null_key) { if($has_lazy_columns) { $sql = $meta->load_sql_with_null_key(\@key_columns, \@key_values, $db); } else { $sql = $meta->load_all_sql_with_null_key(\@key_columns, \@key_values, $db); } } else { if($has_lazy_columns) { $sql = $meta->load_sql(\@key_columns, $db); } else { $sql = $meta->load_all_sql(\@key_columns, $db); } } if(my $lock = $args{'lock'}) { $sql .= ' ' . $db->format_select_lock($self, $lock); } # $meta->prepare_select_options (defunct) $sth = $prepare_cached ? $dbh->prepare_cached($sql, undef, 3) : $dbh->prepare($sql); $Debug && warn "$sql - bind params: ", join(', ', grep { defined } @key_values), "\n"; $sth->execute(grep { defined } @key_values); my %row; $sth->bind_columns(undef, \@row{@$column_names}); $loaded_ok = defined $sth->fetch; # The load() query shouldn't find more than one row anyway, # but DBD::SQLite demands this :-/ # XXX: Recent versions of DBD::SQLite seem to have cured this. # XXX: Safe to remove? $sth->finish; if($loaded_ok) { my $methods = $meta->column_mutator_method_names_hash; # Empty existing object? #%$self = (db => $self->db, meta => $meta, STATE_LOADING() => 1); foreach my $name (@$column_names) { my $method = $methods->{$name}; $self->$method($row{$name}); } # Sneaky init by object replacement #my $object = (ref $self)->new(db => $self->db); # #foreach my $name (@$column_names) #{ # my $method = $methods->{$name}; # $object->$method($row{$name}); #} # #$self = $_[0] = $object; } else { no warnings; $self->error("No such " . ref($self) . ' where ' . join(', ', @key_columns) . ' = ' . join(', ', @key_values)); $self->{'not_found'} = 1; $self->{STATE_IN_DB()} = 0; } }; $error = $@; } if($error) { $self->error(ref $error ? $error : "load() - $error"); $meta->handle_error($self); return undef; } unless($loaded_ok) { my $speculative = exists $args{'speculative'} ? $args{'speculative'} : $meta->default_load_speculative; unless($speculative) { $meta->handle_error($self); } return 0; } $self->{STATE_IN_DB()} = 1; $self->{LOADED_FROM_DRIVER()} = $db->{'driver'}; $self->{MODIFIED_COLUMNS()} = {}; return $self || 1; } sub save { my($self, %args) = @_; my $meta = $self->meta; my $cascade = exists $args{'cascade'} ? $args{'cascade'} : $meta->default_cascade_save; # Keep trigger-encumbered and cascade code in separate code path if($self->{ON_SAVE_ATTR_NAME()} || $cascade) { my $db = $args{'db'} || $self->db || return 0; my $ret = $db->begin_work; $args{'db'} ||= $db; unless($ret) { my $error = $db->error; $self->error(ref $error ? $error : "Could not begin transaction before saving - $error"); $self->meta->handle_error($self); return undef; } my $started_new_tx = ($ret == IN_TRANSACTION) ? 0 : 1; my $error; TRY: { local $@; eval { my %did_set; my %code_args = map { ($_ => $args{$_}) } grep { exists $args{$_} } qw(changes_only prepare_cached cascade); # # Do pre-save stuff # my $todo = $self->{ON_SAVE_ATTR_NAME()}{'pre'}; foreach my $fk_name (keys %{$todo->{'fk'}}) { my $code = $todo->{'fk'}{$fk_name}{'set'} or next; my $object = $code->($self, \%code_args); # Account for objects that evaluate to false to due overloading unless($object || ref $object) { die $self->error; } # Track which rows were set so we can avoid deleting # them later in the "delete on save" code $did_set{'fk'}{$fk_name}{Rose::DB::Object::Util::row_id($object)} = 1; } # # Do the actual save # if(!$args{'insert'} && ($args{'update'} || $self->{STATE_IN_DB()})) { $ret = shift->update(@_); } else { $ret = shift->insert(@_); } # # Do post-save stuff # $todo = $self->{ON_SAVE_ATTR_NAME()}{'post'}; # Foreign keys (and some fk-like relationships) foreach my $fk_name (keys %{$todo->{'fk'}}) { foreach my $item (@{$todo->{'fk'}{$fk_name}{'delete'} || []}) { my $code = $item->{'code'}; my $object = $item->{'object'}; # Don't run the code to delete this object if we just set it above next if($did_set{'fk'}{$fk_name}{Rose::DB::Object::Util::row_id($object)}); $code->($self, \%code_args) or die $self->error; } } if($cascade) { foreach my $fk ($meta->foreign_keys) { # If this object was just set above, just save changes (there # should be none) as a way to continue the cascade local $args{'changes_only'} = 1 if($todo->{'fk'}{$fk->name}{'set'}); my $foreign_object = $fk->object_has_foreign_object($self) || next; if(Rose::DB::Object::Util::has_modified_columns($foreign_object) || Rose::DB::Object::Util::has_modified_children($foreign_object)) { $Debug && warn "$self - save foreign ", $fk->name, " - $foreign_object\n"; $foreign_object->save(%args); } } } # Relationships foreach my $rel_name (keys %{$todo->{'rel'}}) { my $code; # Set value(s) if($code = $todo->{'rel'}{$rel_name}{'set'}) { $code->($self, \%code_args) or die $self->error; } # Delete value(s) if($code = $todo->{'rel'}{$rel_name}{'delete'}) { $code->($self, \%code_args) or die $self->error; } # Add value(s) if($code = $todo->{'rel'}{$rel_name}{'add'}{'code'}) { $code->($self, \%code_args) or die $self->error; } } if($cascade) { foreach my $rel ($meta->relationships) { # If this object was just set above, just save changes (there # should be none) as a way to continue the cascade local $args{'changes_only'} = 1 if($todo->{'rel'}{$rel->name}{'set'}); my $related_objects = $rel->object_has_related_objects($self) || next; foreach my $related_object (@$related_objects) { if(Rose::DB::Object::Util::has_modified_columns($related_object) || Rose::DB::Object::Util::has_modified_children($related_object)) { $Debug && warn "$self - save related ", $rel->name, " - $related_object\n"; $related_object->save(%args); } } } } if($started_new_tx) { $db->commit or die $db->error; } }; $error = $@; } delete $self->{ON_SAVE_ATTR_NAME()}; if($error) { $self->error($error); $db->rollback or warn $db->error if($started_new_tx); $self->meta->handle_error($self); return 0; } $self->{MODIFIED_COLUMNS()} = {}; return $ret; } else { if(!$args{'insert'} && ($args{'update'} || $self->{STATE_IN_DB()})) { return shift->update(@_); } return shift->insert(@_); } } sub update { my($self, %args) = @_; my $db = $self->db or return 0; my $dbh = $self->dbh or return 0; my $meta = $self->meta; my $prepare_cached = exists $args{'prepare_cached'} ? $args{'prepare_cached'} : $meta->dbi_prepare_cached; my $changes_only = exists $args{'changes_only'} ? $args{'changes_only'} : $meta->default_update_changes_only; local $self->{STATE_SAVING()} = 1; my @key_columns = $meta->primary_key_column_names; my @key_methods = $meta->primary_key_column_accessor_names; my @key_values = grep { defined } map { $self->$_() } @key_methods; # Special case for tables where all columns are part of the primary key return $self || 1 if(@key_columns == $meta->num_columns); # See comment below #my $null_key = 0; #my $found_key = 0; unless(@key_values == @key_columns) { $self->error("Cannot update " . ref($self) . " without a primary key (" . join(', ', @key_columns) . ') with ' . (@key_columns > 1 ? 'non-null values in all columns' : 'a non-null value')); $self->meta->handle_error($self); return undef; } #my $ret = $db->begin_work; # #unless($ret) #{ # my $error = $db->error; # $self->error(ref $error ? $error : "Could not begin transaction before updating - $error"); # return undef; #} # #my $started_new_tx = ($ret == Rose::DB::Constants::IN_TRANSACTION) ? 0 : 1; my $error; TRY: { local $@; eval { #local $self->{STATE_SAVING()} = 1; local $dbh->{'RaiseError'} = 1; my $sth; if($meta->allow_inline_column_values) { # This versions of update_sql_with_inlining is not needed (see comments # in Rose/DB/Object/Metadata.pm for more information) #my($sql, $bind) = # $meta->update_sql_with_inlining($self, \@key_columns, \@key_values); my($sql, $bind, $bind_params); if($changes_only) { # No changes to save... return $self || 1 unless(%{$self->{MODIFIED_COLUMNS()} || {}}); ($sql, $bind, $bind_params) = $meta->update_changes_only_sql_with_inlining($self, \@key_columns); unless($sql) # skip key-only updates { $self->{MODIFIED_COLUMNS()} = {}; return $self || 1; } } else { ($sql, $bind, $bind_params) = $meta->update_sql_with_inlining($self, \@key_columns); } if($Debug) { no warnings; warn "$sql - bind params: ", join(', ', @$bind, @key_values), "\n"; } $sth = $dbh->prepare($sql); #, $meta->prepare_update_options); if($bind_params) { my $i = 1; foreach my $value (@$bind) { $sth->bind_param($i, $value, $bind_params->[$i - 1]); $i++; } my $kv_idx = 0; foreach my $column_name (@key_columns) { my $column = $meta->column($column_name); $sth->bind_param($i++, $key_values[$kv_idx++], $column->dbi_bind_param_attrs($db)); } $sth->execute; } else { $sth->execute(@$bind, @key_values); } } else { if($changes_only) { # No changes to save... return $self || 1 unless(%{$self->{MODIFIED_COLUMNS()} || {}}); my($sql, $bind, $columns) = $meta->update_changes_only_sql($self, \@key_columns, $db); unless($sql) # skip key-only updates { $self->{MODIFIED_COLUMNS()} = {}; return $self || 1; } # $meta->prepare_update_options (defunct) my $sth = $prepare_cached ? $dbh->prepare_cached($sql, undef, 3) : $dbh->prepare($sql); if($Debug) { no warnings; warn "$sql - bind params: ", join(', ', @$bind, @key_values), "\n"; } if($meta->dbi_requires_bind_param($db)) { my $i = 1; foreach my $column (@$columns) { my $method = $column->accessor_method_name; $sth->bind_param($i++, $self->$method(), $column->dbi_bind_param_attrs($db)); } my $kv_idx = 0; foreach my $column_name (@key_columns) { my $column = $meta->column($column_name); $sth->bind_param($i++, $key_values[$kv_idx++], $column->dbi_bind_param_attrs($db)); } $sth->execute; } else { $sth->execute(@$bind, @key_values); } } elsif($meta->has_lazy_columns) { my($sql, $bind, $columns) = $meta->update_sql($self, \@key_columns, $db); # $meta->prepare_update_options (defunct) my $sth = $prepare_cached ? $dbh->prepare_cached($sql, undef, 3) : $dbh->prepare($sql); if($Debug) { no warnings; warn "$sql - bind params: ", join(', ', @$bind, @key_values), "\n"; } if($meta->dbi_requires_bind_param($db)) { my $i = 1; foreach my $column (@$columns) { my $method = $column->accessor_method_name; $sth->bind_param($i++, $self->$method(), $column->dbi_bind_param_attrs($db)); } my $kv_idx = 0; foreach my $column_name (@key_columns) { my $column = $meta->column($column_name); $sth->bind_param($i++, $key_values[$kv_idx++], $column->dbi_bind_param_attrs($db)); } $sth->execute; } else { $sth->execute(@$bind, @key_values); } } else { my $sql = $meta->update_all_sql(\@key_columns, $db); # $meta->prepare_update_options (defunct) my $sth = $prepare_cached ? $dbh->prepare_cached($sql, undef, 3) : $dbh->prepare($sql); my %key = map { ($_ => 1) } @key_methods; my $method_names = $meta->column_accessor_method_names; if($Debug) { no warnings; warn "$sql - bind params: ", join(', ', (map { $self->$_() } grep { !$key{$_} } @$method_names), grep { defined } @key_values), "\n"; } if($meta->dbi_requires_bind_param($db)) { my $i = 1; foreach my $column (grep { !$key{$_->name} } $meta->columns_ordered) { my $method = $column->accessor_method_name; $sth->bind_param($i++, $self->$method(), $column->dbi_bind_param_attrs($db)); } foreach my $column_name (@key_columns) { my $column = $meta->column($column_name); my $method = $column->accessor_method_name; $sth->bind_param($i++, $self->$method(), $column->dbi_bind_param_attrs($db)); } $sth->execute; } else { $sth->execute( (map { $self->$_() } grep { !$key{$_} } @$method_names), @key_values); } } } #if($started_new_tx) #{ # $db->commit or die $db->error; #} }; $error = $@; } if($error) { $self->error(ref $error ? $error : "update() - $error"); #$db->rollback or warn $db->error if($started_new_tx); $self->meta->handle_error($self); return 0; } $self->{STATE_IN_DB()} = 1; $self->{MODIFIED_COLUMNS()} = {}; return $self || 1; } sub insert { my($self, %args) = @_; my $db = $self->db or return 0; my $dbh = $self->dbh or return 0; my $meta = $self->meta; my $prepare_cached = exists $args{'prepare_cached'} ? $args{'prepare_cached'} : $meta->dbi_prepare_cached; my $changes_only = exists $args{'changes_only'} ? $args{'changes_only'} : $meta->default_insert_changes_only; local $self->{STATE_SAVING()} = 1; my @pk_methods = $meta->primary_key_column_accessor_names; my @pk_values = grep { defined } map { $self->$_() } @pk_methods; #my $ret = $db->begin_work; # #unless($ret) #{ # my $error = $db->error; # $self->error(ref $error ? $error : "Could not begin transaction before inserting - $error"); # return undef; #} # #my $started_new_tx = ($ret > 0) ? 1 : 0; my $using_pk_placeholders = 0; unless(@pk_values == @pk_methods || $args{'on_duplicate_key_update'}) { my @generated_pk_values = $meta->generate_primary_key_values($db); unless(@generated_pk_values) { @generated_pk_values = $meta->generate_primary_key_placeholders($db); $using_pk_placeholders = 1; } unless(@generated_pk_values == @pk_methods) { my $s = (@pk_values == 1 ? '' : 's'); $self->error("Could not generate primary key$s for column$s " . join(', ', @pk_methods)); $self->meta->handle_error($self); return undef; } my @pk_set_methods = map { $meta->column_mutator_method_name($_) } $meta->primary_key_column_names; my $i = 0; foreach my $name (@pk_set_methods) { my $pk_value = shift @generated_pk_values; next unless(defined $pk_value); $self->$name($pk_value); } } my $error; TRY: { local $@; eval { #local $self->{STATE_SAVING()} = 1; local $dbh->{'RaiseError'} = 1; #my $options = $meta->prepare_insert_options; my $sth; if($meta->allow_inline_column_values) { my($sql, $bind, $bind_params); if($args{'on_duplicate_key_update'}) { ($sql, $bind, $bind_params) = $meta->insert_and_on_duplicate_key_update_with_inlining_sql( $self, $db, $changes_only); } elsif($changes_only) { ($sql, $bind, $bind_params) = $meta->insert_changes_only_sql_with_inlining($self); } else { ($sql, $bind, $bind_params) = $meta->insert_sql_with_inlining($self); } if($Debug) { no warnings; warn "$sql - bind params: ", join(', ', @$bind), "\n"; } $sth = $dbh->prepare($sql); #, $options); if($bind_params) { my $i = 1; foreach my $value (@$bind) { $sth->bind_param($i, $value, $bind_params->[$i - 1]); $i++; } $sth->execute; } else { $sth->execute(@$bind); } } else { my $column_names = $meta->column_names; if($args{'on_duplicate_key_update'} || $changes_only) { my($sql, $bind, $columns); if($args{'on_duplicate_key_update'}) { ($sql, $bind, $columns) = $meta->insert_and_on_duplicate_key_update_sql( $self, $db, $changes_only); } else { ($sql, $bind, $columns) = $meta->insert_changes_only_sql($self, $db); } if($Debug) { no warnings; warn $sql, " - bind params: @$bind\n"; } $sth = $prepare_cached ? $dbh->prepare_cached($sql, undef, 3) : $dbh->prepare($sql); if($meta->dbi_requires_bind_param($db)) { my $i = 1; foreach my $column (@$columns) { my $method = $column->accessor_method_name; $sth->bind_param($i++, $self->$method(), $column->dbi_bind_param_attrs($db)); } $sth->execute; } else { $sth->execute(@$bind); } } else { $sth = $prepare_cached ? $dbh->prepare_cached($meta->insert_sql($db), undef, 3) : $dbh->prepare($meta->insert_sql($db)); if($Debug) { no warnings; warn $meta->insert_sql($db), " - bind params: ", join(', ', (map {$self->$_()} $meta->column_accessor_method_names)), "\n"; } #$sth->execute(map { $self->$_() } $meta->column_accessor_method_names); if($meta->dbi_requires_bind_param($db)) { my $i = 1; foreach my $column ($meta->columns_ordered) { my $method = $column->accessor_method_name; $sth->bind_param($i++, $self->$method(), $column->dbi_bind_param_attrs($db)); } $sth->execute; } else { $sth->execute(map { $self->$_() } $meta->column_accessor_method_names); } } } if(@pk_methods == 1) { my $get_pk = $pk_methods[0]; if($using_pk_placeholders || !defined $self->$get_pk()) { local $self->{STATE_LOADING()} = 1; my $set_pk = $meta->column_mutator_method_name($meta->primary_key_column_names); #$self->$set_pk($db->last_insertid_from_sth($sth, $self)); $self->$set_pk($db->last_insertid_from_sth($sth)); $self->{STATE_IN_DB()} = 1; } elsif(!$using_pk_placeholders && defined $self->$get_pk()) { $self->{STATE_IN_DB()} = 1; } } elsif(@pk_values == @pk_methods) { $self->{STATE_IN_DB()} = 1; } elsif(!$using_pk_placeholders) { my $have_pk = 1; my @pk_set_methods = $meta->primary_key_column_mutator_names; my $i = 0; my $got_last_insert_id = 0; foreach my $pk (@pk_methods) { unless(defined $self->$pk()) { # XXX: This clause assumes that any db that uses last_insert_id # XXX: can only have one such id per table. This is currently # XXX: true for the supported dbs: MySQL, Pg, SQLite, Informix. if($got_last_insert_id) { $have_pk = 0; last; } elsif(my $pk_val = $db->last_insertid_from_sth($sth)) { my $set_pk = $pk_set_methods[$i]; $self->$set_pk($pk_val); $got_last_insert_id = 1; } else { $have_pk = 0; last; } } $i++; } $self->{STATE_IN_DB()} = $have_pk; } #if($started_new_tx) #{ # $db->commit or die $db->error; #} }; $error = $@; } if($error) { $self->error(ref $error ? $error : "insert() - $error"); #$db->rollback or warn $db->error if($started_new_tx); $self->meta->handle_error($self); return 0; } $self->{MODIFIED_COLUMNS()} = {}; return $self || 1; } my %CASCADE_VALUES = (delete => 'delete', null => 'null', 1 => 'delete'); sub delete { my($self, %args) = @_; my $meta = $self->meta; my $prepare_cached = exists $args{'prepare_cached'} ? $args{'prepare_cached'} : $meta->dbi_prepare_cached; local $self->{STATE_SAVING()} = 1; my @pk_methods = $meta->primary_key_column_accessor_names; my @pk_values = grep { defined } map { $self->$_() } @pk_methods; unless(@pk_values == @pk_methods) { $self->error("Cannot delete " . ref($self) . " without a primary key (" . join(', ', @pk_methods) . ')'); $self->meta->handle_error($self); return 0; } # Totally separate code path for cascaded delete if(my $cascade = $args{'cascade'}) { unless(exists $CASCADE_VALUES{$cascade}) { Carp::croak "Illegal value for 'cascade' parameter: '$cascade'. ", "Valid values are 'delete', 'null', and '1'"; } $cascade = $CASCADE_VALUES{$cascade}; my $mgr_error_mode = Rose::DB::Object::Manager->error_mode; my($db, $started_new_tx, $error); TRY: { local $@; eval { $db = $self->db; my $meta = $self->meta; my $ret = $db->begin_work; unless(defined $ret) { die 'Could not begin transaction before deleting with cascade - ', $db->error; } $started_new_tx = ($ret == IN_TRANSACTION) ? 0 : 1; unless($self->{STATE_IN_DB()}) { $self->load or die "Could not load in preparation for cascading delete: ", $self->error; } Rose::DB::Object::Manager->error_mode('fatal'); my @one_to_one_rels; # Process all the rows for each "... to many" relationship REL: foreach my $relationship ($meta->relationships) { my $rel_type = $relationship->type; if($rel_type eq 'one to many') { my $column_map = $relationship->column_map; my @query; foreach my $local_column (keys %$column_map) { my $foreign_column = $column_map->{$local_column}; my $method = $meta->column_accessor_method_name($local_column); my $value = $self->$method(); # XXX: Comment this out to allow null keys next REL unless(defined $value); push(@query, $foreign_column => $value); } if($cascade eq 'delete') { Rose::DB::Object::Manager->delete_objects( db => $db, object_class => $relationship->class, where => \@query); } elsif($cascade eq 'null') { my %set = map { $_ => undef } values(%$column_map); Rose::DB::Object::Manager->update_objects( db => $db, object_class => $relationship->class, set => \%set, where => \@query); } else { Carp::confess "Illegal cascade value '$cascade' snuck through" } } elsif($rel_type eq 'many to many') { my $map_class = $relationship->map_class; my $map_from = $relationship->map_from; my $map_from_relationship = $map_class->meta->foreign_key($map_from) || $map_class->meta->relationship($map_from) || Carp::confess "No foreign key or 'many to one' relationship ", "named '$map_from' in class $map_class"; my $key_columns = $map_from_relationship->key_columns; my @query; # "Local" here means "local to the mapping table" foreach my $local_column (keys %$key_columns) { my $foreign_column = $key_columns->{$local_column}; my $method = $meta->column_accessor_method_name($foreign_column); my $value = $self->$method(); # XXX: Comment this out to allow null keys next REL unless(defined $value); push(@query, $local_column => $value); } if($cascade eq 'delete') { Rose::DB::Object::Manager->delete_objects( db => $db, object_class => $map_class, where => \@query); } elsif($cascade eq 'null') { my %set = map { $_ => undef } keys(%$key_columns); Rose::DB::Object::Manager->update_objects( db => $db, object_class => $map_class, set => \%set, where => \@query); } else { Carp::confess "Illegal cascade value '$cascade' snuck through" } } elsif($rel_type eq 'one to one') { push(@one_to_one_rels, $relationship); } } # Delete the object itself my $dbh = $db->dbh or die "Could not get dbh: ", $self->error; #local $self->{STATE_SAVING()} = 1; local $dbh->{'RaiseError'} = 1; # $meta->prepare_delete_options (defunct) my $sth = $prepare_cached ? $dbh->prepare_cached($meta->delete_sql($db), undef, 3) : $dbh->prepare($meta->delete_sql($db)); $Debug && warn $meta->delete_sql($db), " - bind params: ", join(', ', @pk_values), "\n"; $sth->execute(@pk_values); unless($sth->rows > 0) { $self->error("Did not delete " . ref($self) . ' where ' . join(', ', @pk_methods) . ' = ' . join(', ', @pk_values)); } # Process all rows referred to by "one to one" foreign keys FK: foreach my $fk ($meta->foreign_keys) { next unless($fk->relationship_type eq 'one to one'); my $key_columns = $fk->key_columns; my @query; foreach my $local_column (keys %$key_columns) { my $foreign_column = $key_columns->{$local_column}; my $method = $meta->column_accessor_method_name($local_column); my $value = $self->$method(); # XXX: Comment this out to allow null keys next FK unless(defined $value); push(@query, $foreign_column => $value); } if($cascade eq 'delete') { Rose::DB::Object::Manager->delete_objects( db => $db, object_class => $fk->class, where => \@query); } elsif($cascade eq 'null') { my %set = map { $_ => undef } values(%$key_columns); Rose::DB::Object::Manager->update_objects( db => $db, object_class => $fk->class, set => \%set, where => \@query); } else { Carp::confess "Illegal cascade value '$cascade' snuck through" } } # Process all the rows for each "one to one" relationship REL: foreach my $relationship (@one_to_one_rels) { my $column_map = $relationship->column_map; my @query; foreach my $local_column (keys %$column_map) { my $foreign_column = $column_map->{$local_column}; my $method = $meta->column_accessor_method_name($local_column); my $value = $self->$method(); # XXX: Comment this out to allow null keys next REL unless(defined $value); push(@query, $foreign_column => $value); } if($cascade eq 'delete') { Rose::DB::Object::Manager->delete_objects( db => $db, object_class => $relationship->class, where => \@query); } elsif($cascade eq 'null') { my %set = map { $_ => undef } values(%$column_map); Rose::DB::Object::Manager->update_objects( db => $db, object_class => $relationship->class, set => \%set, where => \@query); } else { Carp::confess "Illegal cascade value '$cascade' snuck through" } } if($started_new_tx) { $db->commit or die $db->error; } }; $error = $@; } if($error) { Rose::DB::Object::Manager->error_mode($mgr_error_mode); $self->error(ref $error ? $error : "delete() with cascade - $error"); $db->rollback if($db && $started_new_tx); $self->meta->handle_error($self); return 0; } Rose::DB::Object::Manager->error_mode($mgr_error_mode); $self->{STATE_IN_DB()} = 0; return 1; } else { my $db = $self->db or return 0; my $dbh = $db->dbh or return 0; my $error; TRY: { local $@; eval { #local $self->{STATE_SAVING()} = 1; local $dbh->{'RaiseError'} = 1; # $meta->prepare_delete_options (defunct) my $sth = $prepare_cached ? $dbh->prepare_cached($meta->delete_sql($db), undef, 3) : $dbh->prepare($meta->delete_sql($db)); $Debug && warn $meta->delete_sql($db), " - bind params: ", join(', ', @pk_values), "\n"; $sth->execute(@pk_values); unless($sth->rows > 0) { $self->error("Did not delete " . ref($self) . ' where ' . join(', ', @pk_methods) . ' = ' . join(', ', @pk_values)); } }; $error = $@; } if($error) { $self->error(ref $error ? $error : "delete() - $error"); $self->meta->handle_error($self); return 0; } $self->{STATE_IN_DB()} = 0; return 1; } } our $AUTOLOAD; sub AUTOLOAD { my $self = shift; my $msg = ''; TRY: { local $@; # Not sure if this will ever be used, but just in case... eval { my @fks = $self->meta->deferred_foreign_keys; my @rels = $self->meta->deferred_relationships; if(@fks || @rels) { my $class = ref $self; my $tmp_msg =<<"EOF"; Methods for the following relationships and foreign keys were deferred and then never actually created in the class $class. TYPE NAME ---- ---- EOF my $found = 0; foreach my $thing (@fks, @rels) { next unless($thing->parent->class eq $class); $found++; my $type = $thing->isa('Rose::DB::Object::Metadata::Relationship') ? 'Relationship' : $thing->isa('Rose::DB::Object::Metadata::ForeignKey') ? 'Foreign Key' : '???'; $tmp_msg .= sprintf("%-15s %s\n", $type, $thing->name); } $msg = "\n\n$tmp_msg\n" if($tmp_msg && $found); } }; # XXX: Ignoring errors } my $method_type = ref $self ? 'object' : 'class'; if($AUTOLOAD =~ /^(.+)::(.+)$/) { Carp::confess qq(Can't locate $method_type method "$2" via package "$1"$msg); } else # not reached? { Carp::confess qq(Can't locate $method_type method $AUTOLOAD$msg); } } sub DESTROY { } # { # my($self) = shift; # # if($self->{FLAG_DB_IS_PRIVATE()}) # { # if(my $db = $self->{'db'}) # { # #$Debug && warn "$self DISCONNECT\n"; # $db->disconnect; # } # } # } 1; __END__ =head1 NAME Rose::DB::Object - Extensible, high performance object-relational mapper (ORM). =head1 SYNOPSIS ## For an informal overview of Rose::DB::Object, please ## see the Rose::DB::Object::Tutorial documentation. The ## reference documentation follows. ## First, set up your Rose::DB data sources, otherwise you ## won't be able to connect to the database at all. See ## the Rose::DB documentation for more information. For ## a quick start, see the Rose::DB::Tutorial documentation. ## ## Create classes - two possible approaches: ## # # 1. Automatic configuration # package Category; use base qw(Rose::DB::Object); __PACKAGE__->meta->setup ( table => 'categories', auto => 1, ); ... package Price; use base qw(Rose::DB::Object); __PACKAGE__->meta->setup ( table => 'prices', auto => 1, ); ... package Product; use base qw(Rose::DB::Object); __PACKAGE__->meta->setup ( table => 'products', auto => 1, ); # # 2. Manual configuration # package Category; use base qw(Rose::DB::Object); __PACKAGE__->meta->setup ( table => 'categories', columns => [ id => { type => 'int', primary_key => 1 }, name => { type => 'varchar', length => 255 }, description => { type => 'text' }, ], unique_key => 'name', ); ... package Price; use base qw(Rose::DB::Object); __PACKAGE__->meta->setup ( table => 'prices', columns => [ id => { type => 'int', primary_key => 1 }, price => { type => 'decimal' }, region => { type => 'char', length => 3 }, product_id => { type => 'int' } ], unique_key => [ 'product_id', 'region' ], ); ... package Product; use base qw(Rose::DB::Object); __PACKAGE__->meta->setup ( table => 'products', columns => [ id => { type => 'int', primary_key => 1 }, name => { type => 'varchar', length => 255 }, description => { type => 'text' }, category_id => { type => 'int' }, status => { type => 'varchar', check_in => [ 'active', 'inactive' ], default => 'inactive', }, start_date => { type => 'datetime' }, end_date => { type => 'datetime' }, date_created => { type => 'timestamp', default => 'now' }, last_modified => { type => 'timestamp', default => 'now' }, ], unique_key => 'name', foreign_keys => [ category => { class => 'Category', key_columns => { category_id => 'id' }, }, ], relationships => [ prices => { type => 'one to many', class => 'Price', column_map => { id => 'product_id' }, }, ], ); ... # # Example usage # $product = Product->new(id => 123, name => 'GameCube', status => 'active', start_date => '11/5/2001', end_date => '12/1/2007', category_id => 5); $product->save; ... $product = Product->new(id => 123); $product->load; # Load foreign object via "one to one" relationship print $product->category->name; $product->end_date->add(days => 45); $product->save; ... $product = Product->new(id => 456); $product->load; # Load foreign objects via "one to many" relationship print join ' ', $product->prices; ... =head1 DESCRIPTION L is a base class for objects that encapsulate a single row in a database table. L-derived objects are sometimes simply called "L objects" in this documentation for the sake of brevity, but be assured that derivation is the only reasonable way to use this class. L inherits from, and follows the conventions of, L. See the L documentation for more information. For an informal overview of this module distribution, consult the L. =head2 Restrictions L objects can represent rows in almost any database table, subject to the following constraints. =over 4 =item * The database server must be supported by L. =item * The database table must have a primary key. =item * The primary key must not allow null values in any of its columns. =back Although the list above contains the only hard and fast rules, there may be other realities that you'll need to work around. The most common example is the existence of a column name in the database table that conflicts with the name of a method in the L API. There are two possible workarounds: either explicitly alias the column, or define a L. See the L and L methods in the L documentation for more details. There are also varying degrees of support for data types in each database server supported by L. If you have a table that uses a data type not supported by an existing L-derived class, you will have to write your own column class and then map it to a type name using L's L method, yada yada. (Or, of course, you can map the new type to an existing column class.) The entire framework is extensible. This module distribution contains straight-forward implementations of the most common column types, but there's certainly more that can be done. Submissions are welcome. =head2 Features L provides the following functions: =over 4 =item * Create a row in the database by saving a newly constructed object. =item * Initialize an object by loading a row from the database. =item * Update a row by saving a modified object back to the database. =item * Delete a row from the database. =item * Fetch an object referred to by a foreign key in the current object. (i.e., "one to one" and "many to one" relationships.) =item * Fetch multiple objects that refer to the current object, either directly through foreign keys or indirectly through a mapping table. (i.e., "one to many" and "many to many" relationships.) =item * Load an object along with "foreign objects" that are related through any of the supported relationship types. =back Objects can be loaded based on either a primary key or a unique key. Since all tables fronted by Ls must have non-null primary keys, insert, update, and delete operations are done based on the primary key. In addition, its sibling class, L, can do the following: =over 4 =item * Fetch multiple objects from the database using arbitrary query conditions, limits, and offsets. =item * Iterate over a list of objects, fetching from the database in response to each step of the iterator. =item * Fetch objects along with "foreign objects" (related through any of the supported relationship types) in a single query by automatically generating the appropriate SQL join(s). =item * Count the number of objects that match a complex query. =item * Update objects that match a complex query. =item * Delete objects that match a complex query. =back L can be subclassed and used separately (the recommended approach), or it can create object manager methods within a L subclass. See the L documentation for more information. L can parse, coerce, inflate, and deflate column values on your behalf, providing the most convenient possible data representations on the Perl side of the fence, while allowing the programmer to completely forget about the ugly details of the data formats required by the database. Default implementations are included for most common column types, and the framework is completely extensible. Finally, the L can be used to automatically create a suite of L and L subclasses based on the contents of the database. =head2 Configuration Before L can do any useful work, you must register at least one L data source. By default, L instantiates a L object by passing no arguments to its constructor. (See the L method.) If you register a L data source using the default type and domain, this will work fine. Otherwise, you must override the L method in your L subclass and have it return the appropriate L-derived object. To define your own L-derived class, you must describe the table that your class will act as a front-end for. This is done through the L object associated with each L-derived class. The metadata object is accessible via L's L method. Metadata objects can be populated manually or automatically. Both techniques are shown in the L above. The automatic mode works by asking the database itself for the information. There are some caveats to this approach. See the L section of the L documentation for more information. =head2 Serial and Auto-Incremented Columns Most databases provide a way to use a series of arbitrary integers as primary key column values. Some support a native C column data type. Others use a special auto-increment column attribute. L supports at least one such serial or auto-incremented column type in each supported database. In all cases, the L-derived class setup is the same: package My::DB::Object; ... __PACKAGE__->meta->setup ( columns => [ id => { type => 'serial', primary_key => 1, not_null => 1 }, ... ], ... ); (Note that the column doesn't have to be named "id"; it can be named anything.) If the database column uses big integers, use "L" column C instead. Given the column metadata definition above, L will automatically generate and/or retrieve the primary key column value when an object is Ld. Example: $o = My::DB::Object->new(name => 'bud'); # no id specified $o->save; # new id value generated here print "Generated new id value: ", $o->id; This will only work, however, if the corresponding column definition in the database is set up correctly. The exact technique varies from vendor to vendor. Below are examples of primary key column definitions that provide auto-generated values. There's one example for each of the databases supported by L. =over =item * PostgreSQL CREATE TABLE mytable ( id SERIAL PRIMARY KEY, ... ); =item * MySQL CREATE TABLE mytable ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, ... ); =item * SQLite CREATE TABLE mytable ( id INTEGER PRIMARY KEY AUTOINCREMENT, ... ); =item * Informix CREATE TABLE mytable ( id SERIAL NOT NULL PRIMARY KEY, ... ); =item * Oracle Since Oracle does not natively support a serial or auto-incremented column data type, an explicit sequence and trigger must be created to simulate the behavior. The sequence should be named according to this convention: CtableE_EcolumnE_seq>. For example, if the table is named C and the column is named C, then the sequence should be named C. Here's an example database setup. CREATE TABLE mytable ( id INT NOT NULL PRIMARY KEY, ... ); CREATE SEQUENCE mytable_id_seq; CREATE TRIGGER mytable_insert BEFORE INSERT ON mytable FOR EACH ROW BEGIN IF :new.id IS NULL THEN :new.id := mytable_id_seq.nextval; END IF; END; Note the conditional that checks if C<:new.id> is null, which allows the value of the C column to be set explicitly. If a non-NULL value for the C column is provided, then a new value is not pulled from the sequence. If the sequence is not named according to the CtableE_EcolumnE_seq> convention, you can specify the sequence name explicitly in the column metadata. Example: columns => [ id => { type => 'serial', primary_key => 1, not_null => 1, sequence => 'some_other_seq' }, ... =back If the table has a multi-column primary key or does not use a column type that supports auto-generated values, you can define a custom primary key generator function using the L method of the L-derived object that contains the metadata for this class. Example: package MyDBObject; use base qw(Rose::DB::Object); __PACKAGE__->meta->setup ( table => 'mytable', columns => [ k1 => { type => 'int', not_null => 1 }, k2 => { type => 'int', not_null => 1 }, name => { type => 'varchar', length => 255 }, ... ], primary_key_columns => [ 'k1', 'k2' ], primary_key_generator => sub { my($meta, $db) = @_; # Generate primary key values somehow my $k1 = ...; my $k2 = ...; return $k1, $k2; }, ); See the L documentation for more information on custom primary key generators. =head2 Inheritance Simple, single inheritance between L-derived classes is supported. (Multiple inheritance is not currently supported.) The first time the L for a given class is accessed, it is created by making a one-time "deep copy" of the base class's metadata object (as long that the base class has one or more L set). This includes all columns, relationships, foreign keys, and other metadata from the base class. From that point on, the subclass may add to or modify its metadata without affecting any other class. B When using perl 5.8.0 or later, the L module is highly recommended. If it's installed, it will be used to more efficiently clone base-class metadata objects. If the base class has already been L, the subclass must explicitly specify whether it wants to create a new set of column and relationship methods, or merely inherit the methods from the base class. If the subclass contains any metadata modifications that affect method creation, then it must create a new set of methods to reflect those changes. Finally, note that column types cannot be changed "in-place." To change a column type, delete the old column and add a new one with the same name. This can be done in one step with the L method. Example: package BaseClass; use base 'Rose::DB::Object'; __PACKAGE__->meta->setup ( table => 'objects', columns => [ id => { type => 'int', primary_key => 1 }, start => { type => 'scalar' }, ], ); ... package SubClass; use base 'BaseClass'; # Set a default value for this column. __PACKAGE__->meta->column('id')->default(123); # Change the "start" column into a datetime column. __PACKAGE__->meta->replace_column(start => { type => 'datetime' }); # Initialize, replacing any inherited methods with newly created ones __PACKAGE__->meta->initialize(replace_existing => 1); ... $b = BaseClass->new; $id = $b->id; # undef $b->start('1/2/2003'); print $b->start; # '1/2/2003' (plain string) $s = SubClass->new; $id = $s->id; # 123 $b->start('1/2/2003'); # Value is converted to a DateTime object print $b->start->strftime('%B'); # 'January' To preserve all inherited methods in a subclass, do this instead: package SubClass; use base 'BaseClass'; __PACKAGE__->meta->initialize(preserve_existing => 1); =head2 Error Handling Error handling for L-derived objects is controlled by the L method of the L object associated with the class (accessible via the L method). The default setting is "fatal", which means that L methods will L if they encounter an error. B The error return values described in the L documentation are only relevant when the error mode is set to something "non-fatal." In other words, if an error occurs, you'll never see any of those return values if the selected error mode Ls or Ls or otherwise throws an exception when an error occurs. =head1 CONSTRUCTOR =over 4 =item B Returns a new L constructed according to PARAMS, where PARAMS are name/value pairs. Any object method is a valid parameter name. =back =head1 CLASS METHODS =over 4 =item B Returns the L-derived object used to access the database in the absence of an explicit L value. The default implementation simply calls Lnew()|Rose::DB/new> with no arguments. Override this method in your subclass in order to use a different default data source. B This method must be callable as both an object method and a class method. =item B Returns the L-derived object associated with this class. This object describes the database table whose rows are fronted by this class: the name of the table, its columns, unique keys, foreign keys, etc. See the L documentation for more information. =item B Return the name of the L-derived class used to store this object's metadata. Subclasses should override this method if they want to use a custom L subclass. (See the source code for L for an example of this.) =back =head1 OBJECT METHODS =over 4 =item B Get or set the L object used to access the database that contains the table whose rows are fronted by the L-derived class. If it does not already exist, this object is created with a simple, argument-less call to Cnew()>. To override this default in a subclass, override the L method and return the L to be used as the new default. =item B Returns the L-derived object used to access the database in the absence of an explicit L value. The default implementation simply calls Lnew()|Rose::DB/new> with no arguments. Override this method in your subclass in order to use a different default data source. B This method must be callable as both an object method and a class method. =item B Get or set the L database handle contained in L. =item B Delete the row represented by the current object. The object must have been previously loaded from the database (or must otherwise have a defined primary key value) in order to be deleted. Returns true if the row was deleted or did not exist, false otherwise. PARAMS are optional name/value pairs. Valid PARAMS are: =over 4 =item B Also process related rows. TYPE must be "delete", "null", or "1". The value "1" is an alias for "delete". Passing an illegal TYPE value will cause a fatal error. For each "one to many" relationship, all of the rows in the foreign ("many") table that reference the current object ("one") will be deleted in "delete" mode, or will have the column(s) that reference the current object set to NULL in "null" mode. For each "many to many" relationship, all of the rows in the "mapping table" that reference the current object will deleted in "delete" mode, or will have the columns that reference the two tables that the mapping table maps between set to NULL in "null" mode. For each "one to one" relationship or foreign key with a "one to one" L, all of the rows in the foreign table that reference the current object will deleted in "delete" mode, or will have the column(s) that reference the current object set to NULL in "null" mode. In all modes, if the L is not currently in a transaction, a new transaction is started. If any part of the cascaded delete fails, the transaction is rolled back. =item B If true, then L's L method will be used (instead of the L method) when preparing the SQL statement that will delete the object. If omitted, the default value is determined by the L's L class method. =back The cascaded delete feature described above plays it safe by only deleting rows that are not referenced by any other rows (according to the metadata provided by each L-derived class). I B that you implement "cascaded delete" in the database itself, rather than using this feature. It will undoubtedly be faster and more robust than doing it "client-side." You may also want to cascade only to certain tables, or otherwise deviate from the "safe" plan. If your database supports automatic cascaded delete and/or triggers, please consider using these features. =item B Returns the text message associated with the last error that occurred. =item B Insert the current object to the database table. This method should only be used when you're absolutely sure that you want to B the current object to be inserted, rather than updated. It is recommended that you use the L method instead of this one in most circumstances. The L method will "do the right thing," executing an insert or update as appropriate for the current situation. PARAMS are optional name/value pairs. Valid PARAMS are: =over 4 =item B If true, then only the columns whose values have been modified will be included in the insert query. Otherwise, all columns will be included. Note that any column that has a L value set in its L is considered "modified" during an insert operation. If omitted, the default value of this parameter is determined by the L's L class method, which returns false by default. =item B If true, then L's L method will be used (instead of the L method) when preparing the SQL statement that will insert the object. If omitted, the default value is determined by the L's L class method. =back Returns true if the row was inserted successfully, false otherwise. The true value returned on success will be the object itself. If the object Ls its boolean value such that it is not true, then a true value will be returned instead of the object itself. =item B Load a row from the database table, initializing the object with the values from that row. An object can be loaded based on either a primary key or a unique key. Returns true if the row was loaded successfully, undef if the row could not be loaded due to an error, or zero (0) if the row does not exist. The true value returned on success will be the object itself. If the object Ls its boolean value such that it is not true, then a true value will be returned instead of the object itself. When loading based on a unique key, unique keys are considered in the order in which they were defined in the L for this class. If the object has defined values for every column in a unique key, then that key is used. If no such key is found, then the first key for which the object has at least one defined value is used. PARAMS are optional name/value pairs. Valid PARAMS are: =over 4 =item B If true, this parameter is translated to be the equivalent of passing the L parameter and setting the C to C. For example, these are both equivalent: $object->load(for_update => 1); $object->load(lock => { type => 'for update' }); See the L parameter below for more information. =item B Load the object using some form of locking. These lock directives have database-specific behavior and not all directives are supported by all databases. The value should be a reference to a hash or a TYPE string, which is equivalent to setting the value of the C key in the hash reference form. For example, these are both equivalent: $object->load(lock => 'for update'); $object->load(lock => { type => 'for update' }); Valid hash keys are: =over 4 =item B A reference to an array of column names to lock. References to scalars will be de-referenced and used as-is, included literally in the SQL locking clause. =item C If true, do not wait to acquire the lock. If supported, this is usually by adding a C directive to the SQL. =item C The lock type. Valid values for TYPE are C and C. This parameter is required unless the L parameter was passed with a true value. =item C Wait for the specified TIME (generally seconds) before giving up acquiring the lock. If supported, this is usually by adding a C clause to the SQL. =back =item B If true, then all columns will be fetched from the database, even L columns. If omitted, the default is false. =item B If true, then L's L method will be used (instead of the L method) when preparing the SQL query that will load the object. If omitted, the default value is determined by the L's L class method. =item B If this parameter is passed with a true value, and if the load failed because the row was L, then the L setting is ignored and zero (0) is returned. In the absence of an explicitly set value, this parameter defaults to the value returned my the L's L method. =item B Use the unique key Ld KEY to load the object. This overrides the unique key selection process described above. The key must have a defined value in at least one of its L. =item B Load the object and the specified "foreign objects" simultaneously. OBJECTS should be a reference to an array of L or L names. =back B If you are going to override the L method in your subclass, you I pass an I as the first argument to the method, rather than passing a copy of the object reference. Example: # This is the CORRECT way to override load() while still # calling the base class version of the method. sub load { my $self = $_[0]; # Copy, not shift ... # Do your stuff shift->SUPER::load(@_); # Call superclass } Now here's the wrong way: # This is the WRONG way to override load() while still # calling the base class version of the method. sub load { my $self = shift; # WRONG! The alias to the object is now lost! ... # Do your stuff $self->SUPER::load(@_); # This won't work right! } This requirement exists in order to preserve some sneaky object-replacement optimizations in the base class implementation of L. At some point, those optimizations may change or go away. But if you follow these guidelines, your code will continue to work no matter what. =item B Returns true if the previous call to L failed because a row in the database table with the specified primary or unique key did not exist, false otherwise. =item B Returns the L object associated with this class. This object describes the database table whose rows are fronted by this class: the name of the table, its columns, unique keys, foreign keys, etc. See the L documentation for more information. =item B Save the current object to the database table. In the absence of PARAMS, if the object was previously Led from the database, the row will be Ld. Otherwise, a new row will be Led. PARAMS are name/value pairs. Valid PARAMS are listed below. Actions associated with sub-objects that were added or deleted using one of the "*_on_save" relationship or foreign key method types are also performed when this method is called. If there are any such actions to perform, a new transaction is started if the L is not already in one, and L is called if any of the actions fail during the L. Example: $product = Product->new(name => 'Sled'); $vendor = Vendor->new(name => 'Acme'); $product->vendor($vendor); # Product and vendor records created and linked together, # all within a single transaction. $product->save; See the "making methods" sections of the L and L documentation for a description of the "method map" associated with each relationship and foreign key. Only the actions initiated through one of the "*_on_save" method types are handled when L is called. See the documentation for each individual "*_on_save" method type for more specific information. Valid parameters to L are: =over 4 =item B If true, then sub-objects related to this object through a foreign key or relationship that have been previously loaded using methods called on this object and that contain unsaved changes will be L after the parent object is saved. This proceeds recursively through all sub-objects. (All other parameters to the original call to L are also passed on when saving sub-objects.) All database operations are done within a single transaction. If the L is not currently in a transaction, a new transaction is started. If any part of the cascaded save fails, the transaction is rolled back. If omitted, the default value of this parameter is determined by the L's L class method, which returns false by default. Example: $p = Product->new(id => 123)->load; print join(', ', $p->colors); # related Color objects loaded $p->colors->[0]->code('zzz'); # one Color object is modified # The Product object and the modified Color object are saved $p->save(cascade => 1); =item B If true, then only the columns whose values have been modified will be included in the insert or update query. Otherwise, all eligible columns will be included. Note that any column that has a L value set in its L is considered "modified" during an insert operation. If omitted, the default value of this parameter is determined by the L's L class method on update, and the L class method on insert, both of which return false by default. =item B If set to a true value, then an L is attempted, regardless of whether or not the object was previously Led from the database. =item B If true, then L's L method will be used (instead of the L method) when preparing the SQL statement that will save the object. If omitted, the default value is determined by the L's L class method. =item B If set to a true value, then an L is attempted, regardless of whether or not the object was previously Led from the database. =back It is an error to pass both the C and C parameters in a single call. Returns true if the row was inserted or updated successfully, false otherwise. The true value returned on success will be the object itself. If the object Ls its boolean value such that it is not true, then a true value will be returned instead of the object itself. If an insert was performed and the primary key is a single column that supports auto-generated values, then the object accessor for the primary key column will contain the auto-generated value. See the L section for more information. =item B Update the current object in the database table. This method should only be used when you're absolutely sure that you want to B the current object to be updated, rather than inserted. It is recommended that you use the L method instead of this one in most circumstances. The L method will "do the right thing," executing an insert or update as appropriate for the current situation. PARAMS are optional name/value pairs. Valid PARAMS are: =over 4 =item B If true, then only the columns whose values have been modified will be updated. Otherwise, all columns whose values have been loaded from the database will be updated. If omitted, the default value of this parameter is determined by the L's L class method, which returns false by default. =item B If true, then L's L method will be used (instead of the L method) when preparing the SQL statement that will insert the object. If omitted, the default value of this parameter is determined by the L's L class method. =back Returns true if the row was updated successfully, false otherwise. The true value returned on success will be the object itself. If the object Ls its boolean value such that it is not true, then a true value will be returned instead of the object itself. =back =head1 RESERVED METHODS As described in the L documentation, each column in the database table has an associated get/set accessor method in the L. Since the L API already defines many methods (L, L, L, etc.), accessor methods for columns that share the name of an existing method pose a problem. The solution is to alias such columns using L's L method. Here is a list of method names reserved by the L API. If you have a column with one of these names, you must alias it. db dbh delete DESTROY error init_db _init_db insert load meta meta_class not_found save update Note that not all of these methods are public. These methods do not suddenly become public just because you now know their names! Remember the stated policy of the L web application framework: if a method is not documented, it does not exist. (And no, the list of method names above does not constitute "documentation.") =head1 DEVELOPMENT POLICY The L applies to this, and all C modules. Please install L from CPAN and then run "C" for more information. =head1 SUPPORT For an informal overview of L, consult the L. perldoc Rose::DB::Object::Tutorial Any L questions or problems can be posted to the L mailing list. To subscribe to the list or view the archives, go here: L Although the mailing list is the preferred support mechanism, you can also email the author (see below) or file bugs using the CPAN bug tracking system: L There's also a wiki and other resources linked from the Rose project home page: L =head1 CONTRIBUTORS Bradley C Bailey, Graham Barr, Kostas Chatzikokolakis, David Christensen, Lucian Dragus, Justin Ellison, Perrin Harkins, Cees Hek, Benjamin Hitz, Dave Howorth, Peter Karman, Ed Loehr, Adam Mackler, Michael Reece, Thomas Whaples, Douglas Wilson, Teodor Zlatanov =head1 AUTHOR John C. Siracusa (siracusa@gmail.com) =head1 LICENSE Copyright (c) 2010 by John C. Siracusa. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Rose-DB-Object-0.820/lib/Rose/DB/Object/000750 000765 000024 00000000000 14065626016 017375 5ustar00johnstaff000000 000000 Rose-DB-Object-0.820/lib/Rose/DB/Object/MakeMethods/000750 000765 000024 00000000000 14065626016 021576 5ustar00johnstaff000000 000000 Rose-DB-Object-0.820/lib/Rose/DB/Object/Exception.pm000755 000765 000024 00000000746 12502134373 021702 0ustar00johnstaff000000 000000 package Rose::DB::Object::Exception; use strict; use Rose::Object; our @ISA = qw(Rose::Object); our $VERSION = '0.01'; use overload ( '""' => sub { shift->message }, fallback => 1, ); use Rose::Object::MakeMethods::Generic ( scalar => [ 'message', 'code', ], ); sub init { my($self) = shift; @_ = (message => @_) if(@_ == 1); $self->SUPER::init(@_); } package Rose::DB::Object::Exception::ClassNotReady; our @ISA = qw(Rose::DB::Object::Exception); 1; Rose-DB-Object-0.820/lib/Rose/DB/Object/Tutorial.pod000755 000765 000024 00000305616 13604626737 021737 0ustar00johnstaff000000 000000 =head1 NAME Rose::DB::Object::Tutorial - A guided tour of the basics of Rose::DB::Object =head1 INTRODUCTION This document provides a step-by-step introduction to the L module distribution. It demonstrates all of the important features using a semi-realistic example database. This tutorial does not replace the actual documentation for each module, however. The "reference" documentation found in each ".pm" file is still essential, and contains some good examples of its own. This tutorial provides a gradual introduction to L. It also describes "best practices" for using L in the most robust, maintainable manner. If you're just trying to get a feel for what's possible, you can L and take a look at the completed example database and associated Perl code. But I recommend reading the tutorial from start to finish at least once. The examples will start simple and get progressively more complex. You, the developer, have to decide which level of complexity or abstraction is appropriate for your particular task. =head1 CONVENTIONS Some of the examples in this tutorial will use the fictional C namespace prefix. Some will use no prefix at all. Your code should use whatever namespace you deem appropriate. Usually, it will be something like C (i.e., your corporation, organization, and/or project). I've chosen to use C or to omit the prefix entirely simply because this produces shorter class names, which will help this tutorial stay within an 80-column width. For the sake of brevity, the C directive and associated "my" declarations have also been omitted from the example code. Needless to say, you should always C in your actual code. Similarly, the traditional "1;" true value used at the end of each ".pm" file has been omitted from the examples. Don't forget to add this to the end of your actual Perl module files. Although most of the examples in this tutorial use the L module to set up inheritance, directly modifying the C<@ISA> package variable usually works just as well. In situations where there are circular relationships between classes, the C form may be preferable because it runs at compile-time, whereas C<@ISA> modification happens at run-time. In either case, it's a good idea to set up inheritance as early as possible in each module. package Product; # Set up inheritance first use base qw(Rose::DB::Object); # Then do other stuff... ... =head1 TUTORIAL =head2 Preface Before doing anything useful with L, it's necessary to create and configure a L subclass through which L-derived objects will access the database. To get up to speed quickly with L, read the L documentation. The rest of this tutorial will assume the existence of a C class created as L in the L. Here's a possible incarnation of the C class. package My::DB; use base qw(Rose::DB); __PACKAGE__->use_private_registry; __PACKAGE__->register_db( driver => 'pg', database => 'mydb', host => 'localhost', username => 'devuser', password => 'mysecret', ); Read the L for an explanation of this code. The PostgreSQL database will be used in the examples in this tutorial, but the features demonstrated will not be specific to that database. If you are following along with a different database, you may have to adjust the specific syntax used in the SQL table creation statements, but all of the same features should be present in some form. This tutorial is based on a fictional database schema for a store-like application. Both the database schema the corresponding Perl classes will evolve over the course of this document. =head2 Getting started Let's start with a single table in our fictional store database. CREATE TABLE products ( id SERIAL NOT NULL PRIMARY KEY, name VARCHAR(255) NOT NULL, price DECIMAL(10,2) NOT NULL DEFAULT 0.00, UNIQUE(name) ); Here's a basic L class to front that table: package Product; use base qw(Rose::DB::Object); __PACKAGE__->meta->setup ( table => 'products', columns => [ qw(id name price) ], pk_columns => 'id', unique_key => 'name', ); The steps are simple: =over 4 =item 1. Inherit from L. =item 2. Name the table. =item 3. Name the columns. =item 4. Name the primary key column(s). =item 5. Add unique keys (if any). =item 6. Initialize. (Implied at the end of the L call) =back Operations 2 through 6 are done through the L method on the L associated with this class. The table must have a primary key, and may have zero or more unique keys. The primary key and each unique key may contain multiple columns. Of course, L it was established that L needs to be set up for any L class to work properly. To that end, this tutorial assumes the existence of a L subclass named L that is set up according to the L of L. We need to make the C class use L. Here's one way to do it: package Product; use My::DB; use base qw(Rose::DB::Object); __PACKAGE__->meta->setup ( table => 'products', columns => [ qw(id name price) ], pk_columns => 'id', unique_key => 'name', ); sub init_db { My::DB->new } Now C will create a L object when it needs to connect to the database. Note that the Cnew> call in C means that each C object will have its own, private C object. See the section below, L<"A brief digression: database objects">, for an explanation of this setup and some alternatives. =head3 Setting up your own base class Looking forward, it's likely that all of our L-derived classes will want to use L objects when connecting to the database. It's tedious to repeat this code in all of those classes. A common base class can provide a single, shared location for that code. package My::DB::Object; use My::DB; use base qw(Rose::DB::Object); sub init_db { My::DB->new } (Again, note that all C-derived objects will get their own C objects given this definition of C. See the L<"digression"|/"A brief digression: database objects"> section below for more information.) Now the C class can inherit from C instead of inheriting from L directly. package Product; use base 'My::DB::Object'; __PACKAGE__->meta->setup ( table => 'products', columns => [ qw(id name price) ], pk_columns => 'id', unique_key => 'name', ); This use of a common base class is strongly recommended. You will see this pattern repeated in the L as well. The creation of seemingly "trivial" subclasses is a cheap and easy way to ensure ease of extensibility later on. For example, imagine we want to add a C method to all of our database objects. If they all inherit directly from C, that's not easy to do. But if they all inherit from C, we can just add the C method to that class. The lesson is simple: when in doubt, subclass. A few minutes spent now can save you a lot more time down the road. =head3 Rose::DB::Object in action Now that we have our C class all set up, let's see what we can do with it. =head4 Get and set column values By default, each column has a combined accessor/mutator method. When passed a value, the column value is set and returned. When called with no arguments, the value is simply returned. $p->name('Bike'); # set name print $p->name; # get name Since L inherits from L, each object method is also a valid constructor argument. $p = Product->new(name => 'Cane', price => 1.99); print $p->price; # 1.99 =head4 Load An object can be loaded based on a primary key. $p = Product->new(id => 1); # primary key $p->load; # Load the object from the database An object can also be loaded based on a unique key: $p = Product->new(name => 'Sled'); # unique key $p->load; # Load the object from the database If there is no row in the database table with the specified primary or unique key value, the call to L will fail. Under the default L, an exception will be thrown. To safely check whether or not such a row exists, use the C parameter. $p = Product->new(id => 1); unless($p->load(speculative => 1)) { print "No such product with id = 1"; } Regardless of the L, L will simply return true or false when the C parameter is used. =head4 Insert To insert a row, create an object and then L it. $p = Product->new(id => 123, name => 'Widget', price => 4.56); $p->save; # Insert the object into the database The default L will throw an exception if anything goes wrong during the save, so we don't have to check the return value. Here's another variation: $p = Product->new(name => 'Widget', price => 1.23); $p->save; print $p->id; # print the auto-generated primary key value Since the primary key of the C table, C, is a SERIAL column, a new primary key value will be automatically generated if one is not specified. After the object is saved, we can retrieve the auto-generated value. =head4 Update To update a row, simply L an object that has been previously Led or Ld. $p1 = Product->new(name => 'Sprocket', price => 9.99); $p1->save; # Insert a new object into the database $p1->price(12.00); $p1->save; # Update the object in the database $p2 = Product->new(id => 1); $p2->load; # Load an existing object $p2->name($p2->name . ' Mark II'); $p2->save; # Update the object in the database =head4 Delete An object can be deleted based on a primary key or a unique key. $p = Product->new(id => 1); # primary key $p->delete; # Delete the object from the database $p = Product->new(name => 'Sled'); # unique key $p->delete; # Delete the object from the database The L method will return true if the row was deleted or did not exist, false otherwise. It works just as well with objects that have been loaded or saved. $p1 = Product->new(name => 'Sprocket', price => 9.99); $p1->save; # Insert a new object into the database $p1->delete; # Now delete the object $p2 = Product->new(id => 1); $p2->load; # Load an existing object $p2->delete; # Now delete the object =head3 Multiple objects The examples above show SELECT, INSERT, UPDATE, and DELETE operations on one row at time based on primary or unique keys. What about manipulating rows based on other criteria? What about manipulating multiple rows simultaneously? Enter L, or just "the manager" for short. But why is there a separate class for dealing with multiple objects? Why not simply add more methods to the object itself? Say, a C method to go alongside L, L, L and friends? There are several reasons. First, it's somewhat "semantically impure" for the class that represents a single row to also be the class that's used to fetch multiple rows. It's also important to keep the object method namespace as sparsely populated as possible. Each new object method prevents a column with the same name from using that method name. L tries to keep the list of L as small as possible. Second, inevitably, classes grow. It's important for the object manager class to be separate from the object class itself so each class can grow happily in isolation, with no potential for namespace or functionality clashes. All of that being said, L does include support for adding manager methods to the object class. Obviously, this practice is not recommended, but it exists if you really want it. Anyway, let's see some examples. Making a manager class is simply a matter of inheriting from L, specifying the object class, and then creating a series of appropriately named wrapper methods. package Product::Manager; use base qw(Rose::DB::Object::Manager); sub object_class { 'Product' } __PACKAGE__->make_manager_methods('products'); The call to L creates the following methods: get_products get_products_iterator get_products_count delete_products update_products The names are pretty much self-explanatory. You can read the L documentation for all the gory details. The important thing to note is that the methods were all named based on the "products" argument to L. You can see how "products" has been incorporated into each of the method names. This naming scheme is just a suggestion. You can name these methods anything you want (using the C parameter to the L call), or you can even write the methods yourself. Each of these methods is a merely a thin wrapper around the generically-named methods in L. The wrappers pass the specified object class to the generic methods. The Perl code for the C class shown above can be generated automatically by calling the L method on the L that's associated with the C class. Similarly, the L method called on the C metadata object will both generate the code and Luate it for you, automating the entire process of creating a manager class from within your L-derived class. package Product; use base qw(Rose::DB::Object); ... # This actually creates the Product::Manager class # as shown in the code sample above. __PACKAGE__->meta->make_manager_class('products'); As the comment says, the call to L will create a standalone C class in memory. See the documentation for the L and L methods for more information. If you decide not to heed my advice, but instead decide to create these methods inside your L-derived class directly, you can do so by calling L from within your object class. package Product; use Rose::DB::Object::Manager; use base 'My::DB::Object'; ... Rose::DB::Object::Manager->make_manager_methods('products'); This will be the last you see of this technique in this tutorial. All of the examples will assume that the recommended approach is used instead. =head4 Fetching objects The most common task for the manager is fetching multiple objects. We'll use the C method to do that. It's based on the L method, which takes many parameters. One (optional) parameter is the now-familiar L object used to connect to the database. This parameter is valid for all L methods. In the absence of this parameter, the L method of the object class will be called in order to create one. Passing no arguments at all will simply fetch every C object in the database. $products = Product::Manager->get_products(); foreach my $product (@$products) { print $product->name, "\n"; } The return value is a reference to an array of C objects. Now let's go to the other extreme. $products = Product::Manager->get_products( query => [ name => { like => '%Hat' }, id => { ge => 7 }, or => [ price => 15.00, price => { lt => 10.00 }, ], ], sort_by => 'name', limit => 10, offset => 50); That call produces SQL that looks something like this: SELECT id, name, price FROM products WHERE name LIKE '%Hat' AND id >= 7 AND (price = 15.00 OR price < 10.00) ORDER BY name LIMIT 10 OFFSET 50 Manager queries support nested boolean logic and several different kinds of comparison operators. For a full explanation of all the options, see the L documentation. The iterator method takes the same kinds of arguments, but returns an iterator that will fetch the objects from the database one at a time. $iterator = Product::Manager->get_products_iterator(...); while($product = $iterator->next) { print $product->id, ' ', $product->name, "\n"; $iterator->finish if(...); # exit early? } print $iterator->total; # total iterated over Note that this is a "real" iterator. Objects not iterated over are not fetched from the database at all. =head4 Counting objects Counting objects is straightforward. The C method takes the same kinds of arguments as C and C. It returns the count. $num_cheap_products = Product::Manager->get_products_count( query => [ price => { lt => 1.00 } ]); =head4 Deleting objects The C method accepts the same kinds of C arguments as the manager methods described above, only it uses the parameter name C instead. $num_rows_deleted = Product::Manager->delete_products( where => [ id => { ne => 123 }, name => { like => 'Wax%' }, ]); =head4 Updating objects The C method accepts the same kinds of arguments as the C method, plus a C parameter to specify the actual update information. $num_rows_updated = Product::Manager->update_products( set => { price => 5.00, }, where => [ price => 4.99, id => { gt => 100 }, ]); =head3 The end of the beginning This section has covered the I usage and functionality of the L module distribution. Using these features alone, you can automate the basic CRUD operations (Create, Retrieve, Update, and Delete) for single or multiple objects. But it's almost a shame to stop at this point. There's a lot more that L can do for you. The "sweet spot" of effort vs. results is much farther along the curve. In the next section, we will expand upon our C class and tap more of L's features. But first... =head3 A brief digression: database objects The L-derived database object used by each L-derived object is available via the L object attribute. $p = Product->new(...); $db = $p->db; # My::DB object You can read the L documentation to explore the capabilities of these db objects. Most of the time, you won't have to be concerned about them. But it's sometime useful to deal with them directly. The first thing to understand is where the database object comes from. If the L attribute doesn't exist, it is created by calling L. The typical C method simply builds a new database object and returns it. (See the L for an explanation of the possible arguments to L, and why there are none in the call below.) package Product; ... sub init_db { My::DB->new } This means that each C object will have its own C object, and therefore (in the absence of modules like L) its own connection to the database. If this not what you want, you can make C return the same C object to every C object. This will make it harder to ensure that the database handle will be closed when all C objects go out of scope, but that may not be important for your application. The easiest way to do this is to call L instead of L. package Product; ... sub init_db { My::DB->new_or_cached } Since C is only called if a C object does not already have a L object, another way to share a single C object with several C objects is to do so explicitly, either by pre-creating the C object: $db = My::DB->new; # will share this db with the Products below $p1 = Product->new(db => $db, ...); $p2 = Product->new(db => $db, ...); $p3 = Product->new(db => $db, ...); or by letting one of the C objects provide the L for the rest. $p1 = Product->new(...); $p2 = Product->new(db => $p1->db, ...); # use $p1's db $p3 = Product->new(db => $p1->db, ...); # use $p1's db A note for L users: when using L, even if each C has its own C object, remember that they will all share a single underlying L database handle. That is, each L-derived object of a given L and L will eventually call L's L method with the same arguments, and therefore return the same, cached database handle when running under L. The L underlying the L method is also L and will cooperate with L. Here's an example where sharing a database object is important: creating several C objects in a single transaction. $db = My::DB->new; $db->begin_work; # Start transaction # Use this $db with each product object $p1 = Product->new(name => 'Bike', db => $db); $p1->save; $p2 = Product->new(name => 'Sled', db => $db); $p2->save; $p3 = Product->new(name => 'Kite', db => $db); $p3->save; if(...) # Now either commit them all or roll them all back { $db->commit; } else { $db->rollback; } Cross-database migration is another important use for explicitly shared L objects. Here's how to move a product from a production database to an archive database. $production_db = My::DB->new('production'); $archive_db = My::DB->new('archive'); # Load bike from production database $p = Product->new(name => 'Bike', db => $production_db); $p->load; # Save the bike into the archive database $p->db($archive_db); $p->save(insert => 1); # force an insert instead of an update # Delete the bike from the production database $p->db($production_db); $p->delete; =head2 Mainstream usage Let's imagine that the C table has expanded. It now looks like this. CREATE TABLE products ( id SERIAL NOT NULL PRIMARY KEY, name VARCHAR(255) NOT NULL, price DECIMAL(10,2) NOT NULL DEFAULT 0.00, status VARCHAR(128) NOT NULL DEFAULT 'inactive' CHECK(status IN ('inactive', 'active', 'defunct')), date_created TIMESTAMP NOT NULL DEFAULT NOW(), release_date TIMESTAMP, UNIQUE(name) ); We could do a straightforward expansion of the C class as designed in the L. package Product; use base 'My::DB::Object'; __PACKAGE__->meta->setup ( table => 'products', columns => [ qw(id name price status date_created release_date) ], pk_columns => 'id', unique_key => 'name', ); But now we're faced with a few problems. First, while the C column only accepts a few pre-defined values, our C object will gladly accept any status value. But maybe that's okay because the database will reject invalid values, causing an exception will be thrown when the object is saved. The date/time fields are more troubling. What is the format of a valid value for a TIMESTAMP column in PostgreSQL? Consulting the PostgreSQL documentation will yield the answer, I suppose. But now all the code that uses C objects has to be sure to format the C and C values accordingly. That's even more difficult if some of those values come from external sources, such as a web form. Worse, what if we decide to change databases in the future? We'd have to hunt down every single place where a C or C value is set and then modify the formatting to match whatever format the new database wants. Oh, and we'll have to look that up too. Blah. Finally, what about all those default values? The C column already had a default value, but now two more columns also have defaults. True, the database will take care of this when a row is inserted, but now the Perl object is diverging more and more from the database representation. Let's solve all of these problems. If we more accurately describe the columns, L will do the rest. package Product; use base 'My::DB::Object'; __PACKAGE__->meta->setup ( table => 'products', columns => [ id => { type => 'serial', primary_key => 1, not_null => 1 }, name => { type => 'varchar', length => 255, not_null => 1 }, price => { type => 'decimal', precision => 10, scale => 2, not_null => 1, default => 0.00 }, status => { type => 'varchar', length => 128, not_null => 1, default => 'inactive', check_in => [ 'inactive', 'active', 'defunct' ], }, date_created => { type => 'timestamp', not_null => 1, default => 'now()' }, release_date => { type => 'timestamp' }, ], unique_key => 'name', allow_inline_column_values => 1, ); Before examining what new functionality this new class gives us, there are a few things to note about the definition. First, the primary key is no longer specified with the L method. Instead, the C column has its C attribute set to a true value in its description. Second, note the default value for the C column. It's a string containing a call to the PL/SQL function C, which can actually only be run within the database. But thanks to the L attribute being set to a true value, L will pass the string "now()" through to the database as-is. In the case of "creation date" columns like this, it's often better to let the database provide the value as close as possible to the very moment the row is created. On the other hand, this will mean that any newly created C object will have a "strange" value for that column (the string "now()") until/unless it is re-Led from the database. It's a trade-off. Let's see the new C class in action. The defaults work as expected. $p = Product->new; print $p->status; # 'inactive' print $p->price; # 0.00 The C method now restricts its input, throwing an exception if the input is invalid. $p->status('nonesuch'); # Boom! Invalid status: 'nonesuch' The timestamp columns now accept any value that L's L method can understand. $p->release_date('2005-01-22 18:00:57'); $p->release_date('12/24/1980 10am'); See the L documentation for a full list of acceptable formats. Inside a C object, date/time information is stored in L objects. $dt = $p->release_date; # DateTime object Since L objects can be modified in-place, doing a formerly thorny task like date math is now trivial. $p->release_date->add(days => 1); The C method also accepts a L object as an input, of course: $p->release_date(DateTime->new(...)); There are even a few convenience functions triggered by passing a name/value pair. # Thursday, December 25th 1980 at 10:00:00 AM print $p->release_date(format => '%A, %B %E %Y at %t'); # Clone the DateTime object, truncate the clone, and return it $month_start = $p->release_date(truncate => 'month'); print $month_start->strftime('%Y-%m-%d'); # 1980-12-01 Conveniently, L queries can also use any values that the corresponding column methods will accept. For example, here's a query that filters on the C column using a L object. $last_week = DateTime->now->subtract(weeks => 1); $products = Product::Manager->get_products( query => [ release_date => { lt => $last_week }, ], sort_by => 'release_date'); The upshot is that you no longer have to be concerned about the details of the date/time format(s) understood by the underlying database. You're also free to use L objects as a convenient interchange format in your code. This ability isn't just limited to date/time columns. Any data type that requires special formatting in the database, and/or is more conveniently dealt with as a more "rich" value on the Perl side of the fence is fair game for this treatment. Some other examples include the L column type, which is represented by a L object on the Perl side, and the L column type which evaluates the "truth" of its arguments and coerces the value accordingly. In all cases, column values are automatically formatted as required by the native column data types in the database. In some circumstances, L can even "fake" a data type for use with a database that does not natively support it. For example, the L column type is natively supported by PostgreSQL, but it will also work with MySQL using a VARCHAR column as a stand-in. Finally, if you're concerned about the performance implications of "inflating" column values from strings and numbers into (relatively) large objects, rest assured that such inflation is only done as needed. For example, an object with ten date/time columns can be loaded, modified, and saved without ever creating a single L object, provided that none of the date/time columns were among those whose values were modified. Put another way, the methods that service the columns have an awareness of the producer and consumer of their data. When data is coming from the database, the column methods accept it as-is. When data is being sent to the database, it is formatted appropriately, if necessary. If a column value was not modified since it was loaded from the database, then the value that was loaded is simply returned as-is. In this way, data can make a round-trip without ever being inflated, deflated, or formatted. This behavior is not a requirement of all column methods, but it is a recommended practice--one followed by all the column classes that are part of the L distribution. =head2 Auto-initialization and the convention manager The C class set up in the previous section is useful, but it also takes significantly more typing to set up. Over the long term, it's still a clear win. On the other hand, a lot of the details in the column descriptions are already known by the database: column types, default values, maximum lengths, etc. It would be handy if we could ask the database for this information instead of looking it up and typing it in manually. This process of interrogating the database in order to extract metadata is called "auto-initialization." There's an L of the L documentation dedicated to the topic. The executive summary is that auto-initialization saves work in the short-run, but with some long-term costs. Read the L for the details. For the purposes of this tutorial, I will simply demonstrate the features, culminating in the suggested best practice. Let's start by applying auto-initialization to the C class. package Product; use base 'My::DB::Object'; __PACKAGE__->meta->table('products'); __PACKAGE__->meta->auto_initialize; Believe it or not, that class is equivalent to the previous incarnation, right down to the details of the columns and the unique key. As long as the table is specified, L will dig all the rest of the information out of the database. Handy! In fact, that class can be shortened even further with the help of the L. package Product; use base 'My::DB::Object'; __PACKAGE__->meta->auto_initialize; Now even the table is left unspecified. How does L know what to do in this case? Why, by convention, of course. The default convention manager dictates that class names are singular and TitleCased, and their corresponding table names are lowercase and plural. Thus, the omitted table name in the C class is, by convention, assumed to be named "products". Like auto-initialization, the convention manager is handy, but may also present some maintenance issues. I tend to favor a more explicitly approach, but I can also imagine scenarios where the convention manager is a good fit. Keep in mind that customized convention managers are possible, allowing individual organizations or projects to define their own conventions. You can read all about it in the L documentation. Anyway, back to auto-initialization. Yes, L will dig out all sorts of interesting and important information for you. Unfortunately, it will dig that information out I. Worse, this class will fail to load at all if a database connection is not immediately available. Auto-initialization seems like something that is best done only once, with the results being saved in a more conventional form. That's just what L's L functions are designed to do. The C family of methods can generate snippets of Perl code, or even entire classes, based on the results of the auto-initialization process. They'll even honor some basic code formatting directives. package Product; use base 'My::DB::Object'; __PACKAGE__->meta->table('products'); __PACKAGE__->meta->auto_initialize; print __PACKAGE__->meta->perl_class_definition(indent => 2, braces => 'bsd'); Here's the output of that print statement. A few long lines were manually wrapped, but it's otherwise unmodified. package Product; use strict; use base 'My::DB::Object'; __PACKAGE__->meta->setup ( table => 'products', columns => [ id => { type => 'integer', not_null => 1 }, name => { type => 'varchar', length => 255, not_null => 1 }, price => { type => 'numeric', default => '0.00', not_null => 1, precision => 10, scale => 2 }, vendor_id => { type => 'integer' }, status => { type => 'varchar', default => 'inactive', length => 128, not_null => 1 }, date_created => { type => 'timestamp', default => 'now()', not_null => 1 }, release_date => { type => 'timestamp' }, ], primary_key_columns => [ 'id' ], unique_keys => [ 'name' ], allow_inline_column_values => 1, ); 1; Copy and paste that output back into the "Product.pm" file and you're in business. The door is open to further automation through scripts that call the methods demonstrated above. Although it's my inclination to work towards a static, explicit type of class definition, the tools are there for those who prefer a more dynamic approach. =head2 Foreign keys When a column in one table references a row in another table, the referring table is said to have a "foreign key." As with primary and unique keys, L supports foreign keys made up of more than one column. In the context of L, a foreign key is a database-supported construct that ensures that any non-null value in a foreign key column actually refers to an existing row in the foreign table. Databases that enforce this constraint are said to support "referential integrity." Foreign keys are only applicable to L-derived classes when the underlying database supports "native" foreign keys and enforces referential integrity. While it's possible to define foreign keys in a L-derived class even if there is no support for them in the database, this is considered bad practice. If you're just trying to express some sort of relationship between two tables, there's a more appropriate way to do so. (More on that in the L.) Let's add a foreign key to the C table. First, we'll need to create the table that the foreign key will reference. CREATE TABLE vendors ( id SERIAL NOT NULL PRIMARY KEY, name VARCHAR(255) NOT NULL, UNIQUE(name) ); When dealing with any kind of inter-table relationship, L requires a L-derived class fronting each participating table. So we need a class for the C table. package Vendor; use base 'My::DB::Object'; __PACKAGE__->meta->setup ( table => 'vendors', columns => [ id => { type => 'serial', primary_key => 1, not_null => 1 }, name => { type => 'varchar', length => 255, not_null => 1 }, ], unique_key => 'name', ); Now we'll add the foreign key to our ever-growing C table. CREATE TABLE products ( id SERIAL NOT NULL PRIMARY KEY, name VARCHAR(255) NOT NULL, price DECIMAL(10,2) NOT NULL DEFAULT 0.00, vendor_id INT REFERENCES vendors (id), status VARCHAR(128) NOT NULL DEFAULT 'inactive' CHECK(status IN ('inactive', 'active', 'defunct')), date_created TIMESTAMP NOT NULL DEFAULT NOW(), release_date TIMESTAMP, UNIQUE(name) ); Finally, here's how the foreign key definition looks in the Perl class. package Product; use base 'My::DB::Object'; __PACKAGE__->meta->setup ( table => 'products', columns => [ id => { type => 'integer', not_null => 1 }, name => { type => 'varchar', length => 255, not_null => 1 }, price => { type => 'numeric', default => '0.00', not_null => 1, precision => 10, scale => 2 }, vendor_id => { type => 'integer' }, status => { type => 'varchar', default => 'inactive', length => 128, not_null => 1 }, date_created => { type => 'timestamp', default => 'now()', not_null => 1 }, release_date => { type => 'timestamp' }, ], primary_key_columns => [ 'id' ], unique_keys => [ 'name' ], allow_inline_column_values => 1, foreign_keys => [ vendor => { class => 'Vendor', key_columns => { vendor_id => 'id' }, }, ], ); Note that a C column is added to the column list. This needs to be done independently of any foreign key definition. It's a new column, so it needs to be in the column list. There's nothing more to it than that. There's also the foreign key definition itself. The name/hashref-value pair passed to the L method is (roughly) shorthand for this. Rose::DB::Object::Metadata::ForeignKey->new( name => 'vendor', class => 'Vendor', key_columns => { vendor_id => 'id' }); In other words, C is the name of the foreign key, and the rest of the information is used to set attributes on the L. You could, in fact, construct your own foreign key objects and pass them to L (or L, etc.) but that would require even more typing. Going in the other direction, since our class and column names match up with what the convention manager expects, we could actually shorten the foreign key setup code to this. foreign_keys => [ 'vendor' ], Given only a foreign key name, the convention manager will derive the C class name and will find the C column in the C class and match it up to the primary key of the C table. As with most things in L class setup, you can be as explicit or as terse as you feel comfortable with, depending on how closely you conform to the expected conventions. So, what does this new C foreign key do for us? Let's add some data and see. Imagine the following two objects. $v = Vendor->new(name => 'Acme')->save; $p = Product->new(name => 'Kite')->save; Note the use of the idiomatic way to create and then save an object in "one step." This is possible because both the L and L methods return the object itself. Anyway, let's link the two objects. One way to do it is to set the column values directly. $p->vendor_id($v->id); $p->save; To use this technique, we must know which columns link to which other columns, of course. But it works. We can see this by calling the method named after the foreign key itself: C. $v = $p->vendor; # Vendor object print $v->name; # "Acme" The C method can be used to link the two objects as well. Let's start over and try it that way: $v = Vendor->new(name => 'Smith')->save; $p = Product->new(name => 'Knife')->save; $p->vendor($v); $p->save; print $p->vendor->name; # "Smith" Remember that there is no column named "vendor" in the "products" table. There is a "vendor_id" column, which has its own C get/set method that accepts and returns an integer value, but that's not what we're doing in the example above. Instead, we're calling the C method, which accepts and returns an entire C object. The C method actually accepts several different kinds of arguments, all of which it inflates into C objects. An already-formed C object was passed above, but other formats are possible. Imagine a new product also made by Smith. $p = Product->new(name => 'Rope')->save; $p->vendor(name => 'Smith'); $p->save; Here the arguments passed to the C method are name/value pairs which will be used to construct the appropriate C object. Since C is a unique key in the C table, the C class can look up the existing vendor named "Smith" and assign it to the "Rope" product. If no vendor named "Smith" existed, one would have been created when the product was saved. In this case, the save process would take place within a transaction (assuming the database supports transactions) to ensure that both the product and vendor are created successfully, or neither is. The name/value pairs can also be provided in a reference to a hash. $p = Product->new(name => 'Rope')->save; $p->vendor({ name => 'Smith' }); $p->save; Here's yet another argument format. Imagine that the "Acme" vendor id is 1. $p = Product->new(name => 'Crate')->save; $p->vendor(1); $p->save; print $p->vendor->name; # "Acme" Like the name/value pair argument format, a primary key value will be used to construct the appropriate object. (This only works if the foreign table has a single-column primary key, of course.) And like before, if such an object doesn't exist, it will be created. But in this case, if no existing vendor object had an C of 1, the attempt to create one would have failed because the C column of the inserted row would have been null. To summarize, the foreign key method can take arguments in these forms. =over 4 =item * An object of the appropriate class. =item * Name/value pairs used to construct such an object. =item * A reference to a hash containing name/value pairs used to construct such an object. =item * A primary key value (but only if the foreign table has a single-column primary key). =back In each case, the foreign object will be added to the database it if does not already exist there. This all happens when the "parent" (C) object is saved. Until then, nothing is stored in the database. There's also another method created in response to the foreign key definition. This one allows the foreign object to be deleted from the database. print $p->vendor->name; # "Acme" $p->delete_vendor(); $p->save; # The "Acme" vendor is deleted from the vendors table Again, the actual database modification takes place when the parent object is saved. Note that this operation will fail if any other rows in the C table still reference the Acme vendor. And again, since this all takes place within a transaction (where supported), the entire operation will fail or succeed as a single unit. Finally, if we want to simply disassociate a product from its vendor, we can simply set the vendor to undef. $p->vendor(undef); # This product has no vendor $p->save; Setting the C column directly has the same effect, of course. $p->vendor_id(undef); # set vendor_id = NULL $p->save; Before moving on to the next section, here's a brief note about auto-initialization and foreign keys. Since foreign keys are a construct of the database itself, the auto-initialization process can actually discover them and create the appropriate foreign key metadata. Since all of the column and table names are still in sync with the expected conventions, the C class can still be defined like this: package Product; use base 'My::DB::Object'; __PACKAGE__->meta->auto_initialize; while retaining all of the abilities demonstrated above. The L method will produce the appropriate foreign key definitions, as expected. package Product; use base 'My::DB::Object'; __PACKAGE__->meta->auto_initialize; print __PACKAGE__->meta->perl_class_definition(indent => 2, braces => 'bsd'); Here's the output. package Product; use base 'My::DB::Object'; __PACKAGE__->meta->setup ( table => 'products', columns => [ id => { type => 'integer', not_null => 1 }, name => { type => 'varchar', length => 255, not_null => 1 }, price => { type => 'numeric', default => '0.00', not_null => 1, precision => 10, scale => 2 }, vendor_id => { type => 'integer' }, status => { type => 'varchar', default => 'inactive', length => 128, not_null => 1 }, date_created => { type => 'timestamp', default => 'now()', not_null => 1 }, release_date => { type => 'timestamp' }, ], primary_key_columns => [ 'id' ], unique_keys => [ 'name' ], allow_inline_column_values => 1, foreign_keys => [ vendor => { class => 'Vendor', key_columns => { vendor_id => 'id' }, }, ], ); 1; =head2 Relationships =head3 One-to-one and many-to-one relationships Foreign keys are a database-native representation of a specific kind of inter-table relationship. This concept can be further generalized to encompass other kinds of relationships as well. But before we delve into that, let's consider the kind of relationship that a foreign key represents. In the product and vendor example in the L, each product has one vendor. (Actually it can have zero or one vendor, since the C column allows NULL values. But for now, we'll leave that aside.) When viewed in terms of the participating tables, things look slightly different. Earlier, we established that several products can have the same vendor. So the inter-table relationship is actually this: many rows from the C table may refer to one row from the C table. L describes inter-table relationships from the perspective of a given table by using the cardinality of the "local" table (C) followed by the cardinality of the "remote" table (C). The foreign key in the C table (and C class) therefore represents a "B" relationship. If the relationship were different and each vendor was only allowed to have a single product, then the relationship would be "one to one." Given only the foreign key definition as it exists in the database, it's not possible to determine whether the relationship is "many to one" or "one to one." The default is "many to one" because that's the less restrictive choice. To override the default, a relationship type string can be included in the foreign key description. foreign_keys => [ vendor => { class => 'Vendor', key_columns => { vendor_id => 'id' }, relationship_type => 'one to one', }, ], (The C parameter may be shortened to C, if desired.) L generalizes all inter-table relationships using a family of aptly named relationship objects. Each inherits from the L base class. Even foreign keys are included under the umbrella of this concept. When foreign key metadata is added to a L-derived class, a corresponding "many to one" or "one to one" relationship is actually added as well. This relationship is simply a proxy for the foreign key. It exists so that the set of relationship objects encompasses all relationships, even those that correspond to foreign keys in the database. This makes iterating over all relationships in a class a simple affair. foreach my $rel (Product->meta->relationships) { print $rel->name, ': ', $rel->type, "\n"; } For the C class, the output is: vendor: many to one Given the two possible cardinalities, "many" and "one", it's easy to come up with a list of all possible inter-table relationships. Here they are, listed with their corresponding relationship object classes. one to one - Rose::DB::Object::Metadata::Relationship::OneToOne one to many - Rose::DB::Object::Metadata::Relationship::OneToMany many to one - Rose::DB::Object::Metadata::Relationship::ManyToOne many to many - Rose::DB::Object::Metadata::Relationship::ManyToMany We've already seen that "one to one" and "many to one" relationships can be represented by foreign keys in the database, but that's not a requirement. It's perfectly possible to have either of those two kinds of relationships in a database that has no native support for foreign keys. (MySQL using the MyISAM storage engine is a common example.) If you find yourself using such a database, there's no reason to lie to your Perl classes by adding foreign key metadata. Instead, simply add a relationship. Here's an example of our C class as it might exist on a database that does not support foreign keys. (The C class is getting larger now, so previously established portions may be omitted from now on.) package Product; use base 'My::DB::Object'; __PACKAGE__->meta->setup ( table => 'products', columns => [... ], pk_columns => 'id', unique_key => 'name', relationships => [ vendor => { type => 'many to one', class => 'Vendor', column_map => { vendor_id => 'id' }, }, ], ); They syntax and semantics are similar to those L for foreign keys. The only slight differences are the names and types of parameters accepted by relationship objects. In the example above, a "many to one" relationship named "vendor" is set up. As demonstrated before, this definition can be reduced much further, allowing the convention manager to fill in the details. But unlike the case with the foreign key definition, where only the name was supplied, we must provide the relationship type as well. relationships => [ vendor => { type => 'many to one' } ], There's an even more convenient shorthand for that: relationships => [ vendor => 'many to one' ], (Again, this all depends on naming the tables, classes, and columns in accordance with the expectations of the L.) The resulting C and C methods behave exactly the same as the methods created on behalf of the foreign key definition. =head3 One-to-many relationships Now let's explore the other two relationship types. We'll start with "one to many" by adding region-specific pricing to our products. First, we'll need a C table. CREATE TABLE prices ( id SERIAL NOT NULL PRIMARY KEY, product_id INT NOT NULL REFERENCES products (id), region CHAR(2) NOT NULL DEFAULT 'US', price DECIMAL(10,2) NOT NULL DEFAULT 0.00, UNIQUE(product_id, region) ); This table needs a corresponding L-derived class, of course. package Price; use base 'My::DB::Object'; __PACKAGE__->meta->setup ( table => 'prices', columns => [ id => { type => 'serial', not_null => 1 }, product_id => { type => 'int', not_null => 1 }, region => { type => 'char', length => 2, not_null => 1 }, price => { type => 'decimal', precision => 10, scale => 2, not_null => 1, default => 0.00 }, ], primary_key_columns => [ 'id' ], unique_key => [ 'product_id', 'region' ], foreign_keys => [ product => { class => 'Product', key_columns => { product_id => 'id' }, }, ], ); The C column can be removed from the C table. ALTER TABLE products DROP COLUMN price; Finally, the C class needs to be modified to reference the C table. package Product; use base 'My::DB::Object'; use Price; use Vendor; __PACKAGE__->meta->setup ( table => 'products', columns => [ ... ], pk_columns => 'id', unique_key => 'name', foreign_keys => [ vendor => { class => 'Vendor', key_columns => { vendor_id => 'id' }, }, ], relationships => [ prices => { type => 'one to many', class => 'Price', column_map => { id => 'product_id' }, }, ], ); Note that both the L for the "one to many" relationship and the L for the foreign key connect "local" columns to "foreign" columns. The C column in the local table (C) is connected to the C column in the foreign table (C): vendor => { key_columns => { vendor_id => 'id' }, ... } The C column in the local table (C) is connected to the C column in the foreign table (C): prices => { column_map => { id => 'product_id' }, ... } This is all from the perspective of the class in which the definitions appear. Note that things are reversed in the C class. package Price; ... __PACKAGE__->meta->setup ( ... foreign_keys => [ product => { class => 'Product', key_columns => { product_id => 'id' }, }, ], ); Here, the C column in the local table (C) is connected to the C column in the foreign table (C). The methods created by "... to many" relationships behave much like their "... to one" and foreign key counterparts. The main difference is that lists or references to arrays of the L argument formats are also acceptable, while name/value pairs outside of a hashref are not. Here's a list of argument types accepted by "many to one" methods like C. =over 4 =item * A list or reference to an array of objects of the appropriate class. =item * A list or reference to an array of hash references containing name/value pairs used to construct such objects. =item * A list or reference to an array of primary key values (but only if the foreign table has a single-column primary key). =back Setting a new list of prices will delete all the old prices. As with foreign keys, any actual database modification happens when the parent object is saved. Here are some examples. $p = Product->new(name => 'Kite'); $p->prices({ price => 1.23, region => 'US' }, { price => 4.56, region => 'UK' }); $p->save; # database is modified here # US: 1.23, UK: 4.56 print join(', ', map { $_->region . ': ' . $_->price } $p->prices); New prices can be added without deleting and resetting the entire list: # Add two prices to the existing list $p->add_prices({ price => 7.89, region => 'DE' }, { price => 1.11, region => 'JP' }); $p->save; # database is modified here Passing a reference to an empty array will cause all the prices to be deleted: $p->prices([]); # delete all prices associated with this product $p->save; # database is modified here =head3 Cascading delete Deleting a product now becomes slightly more interesting. The naive approach fails. $p->delete; # Fatal error! # DBD::Pg::st execute failed: ERROR: update or delete on "products" # violates foreign key constraint "prices_product_id_fkey" on # "prices" # DETAIL: Key (id)=(12345) is still referenced from table "prices". Since rows in the C table now link to rows in the C table, a product cannot be deleted until all of the prices that refer to it are also deleted. There are a few ways to deal with this. The best solution is to add a trigger to the C table itself in the database that makes sure to delete any associated prices before deleting a product. This change will allow the naive approach shown above to work correctly. A less robust solution is necessary if your database does not support triggers. One such solution is to manually delete the prices before deleting the product. This can be done in several ways. The prices can be deleted directly, like this. foreach my $price ($p->prices) { $price->delete; # Delete all associated prices } $p->delete; # Now it's safe to delete the product The list of prices for the product can also be set to an empty list, which will have the effect of deleting all associated prices when the product is saved. $p->prices([]); $p->save; # All associated prices deleted here $p->delete; # Now it's safe to delete the product Finally, the L method can actually automate this process, and do it all inside a transaction as well. $p->delete(cascade => 1); # Delete all associated rows too Again, the recommended approach is to use triggers inside the database itself. But if necessary, these other approaches will work too. =head3 Many-to-many relationships The final relationship type is the most complex. In a "many to many" relationship, a single row in table A may be related to multiple rows in table B, while a single row in table B may also be related to multiple rows in table A. (Confused? A concrete example will follow shortly.) This kind of relationship involves three tables instead of just two. The "local" and "foreign" tables, familiar from the other relationship types described above, still exist, but now there's a third table that connects rows from those two tables. This third table is called the "mapping table," and the L-derived class that fronts it is called the "map class." Let's add such a relationship to our growing family of classes. Imagine that each product may come in several colors. Right away, both the "one to one" and "many to one" relationship types are eliminated since they can only provide a single color for any given product. But wait, isn't a "one to many" relationship suitable? After all, one product may have many colors. Unfortunately, such a relationship is wasteful in this case. Let's see why. Imagine a C table like this. CREATE TABLE colors ( id SERIAL NOT NULL PRIMARY KEY, name VARCHAR(255) NOT NULL, product_id INT NOT NULL REFERENCES products (id) ); Here's a simple C class to front it. package Color; use base 'My::DB::Object'; __PACKAGE__->meta->setup ( table => 'colors', columns => [ id => { type => 'serial', primary_key => 1, not_null => 1 }, name => { type => 'varchar', length => 255, not_null => 1 }, product_id => { type => 'int', not_null => 1 }, ], foreign_keys => [ product => { class => 'Product', key_columns => { product_id => 'id' }, }, ], ); Finally, let's add the "one to many" relationship to the C class. package Product; use base 'My::DB::Object'; __PACKAGE__->meta->setup ( ... relationships => [ colors => { type => 'one to many', class => 'Color', column_map => { id => 'product_id' }, }, ... ], ); It works as expected. $p1 = Product->new(id => 10, name => 'Sled', colors => [ { name => 'red' }, { name => 'green' }, ]); $p1->save; $p2 = Product->new(id => 20, name => 'Kite', colors => [ { name => 'blue' }, { name => 'green' }, { name => 'red' }, ]); $p2->save; But now look at the contents of the C table in the database. mydb=# select * from colors; id | name | product_id ----+-------+------------ 1 | red | 10 2 | green | 10 3 | blue | 20 4 | green | 20 5 | red | 20 Notice that the colors "green" and "red" appear twice. Now imagine that there are 50,000 products. What are the odds that there will be more than a few colors in common among them? This is a poor database design. To fix it, we must recognize that colors will be shared among products, since the set of possible colors is relatively small compared to the set of possible products. One product may have many colors, but one color may also belong to many products. And there you have it: a textbook "many to many" relationship. Let's redesign this relationship in "many to many" form, starting with a new version of the C table. CREATE TABLE colors ( id SERIAL NOT NULL PRIMARY KEY, name VARCHAR(255) NOT NULL, UNIQUE(name) ); Since each color will now appear only once in this table, we can make the C column a unique key. Here's the new C class. package Color; use base 'My::DB::Object'; __PACKAGE__->meta->setup ( table => 'colors', columns => [ id => { type => 'serial', primary_key => 1, not_null => 1 }, name => { type => 'varchar', length => 255, not_null => 1 }, ], unique_key => 'name', ); Since the C table no longer has a foreign key that points to the C table, we need some way to connect the two tables: a mapping table. CREATE TABLE product_color_map ( product_id INT NOT NULL REFERENCES products (id), color_id INT NOT NULL REFERENCES colors (id), PRIMARY KEY(product_id, color_id) ); Note that there's no reason for a separate primary key column in this table. We'll use a two-column primary key instead. Here's the map class. package ProductColorMap; use base 'My::DB::Object'; __PACKAGE__->meta->setup ( table => 'product_color_map', columns => [ product_id => { type => 'int', not_null => 1 }, color_id => { type => 'int', not_null => 1 }, ], primary_key_columns => [ 'product_id', 'color_id' ], foreign_keys => [ product => { class => 'Product', key_columns => { product_id => 'id' }, }, color => { class => 'Color', key_columns => { color_id => 'id' }, }, ], ); It's important that the map class have either a foreign key or a "many to one" relationship pointing to each of the tables that it maps between. In this case, there are two foreign keys. Finally, here's the "many to many" relationship definition in the C class. package Product; ... __PACKAGE__->meta->setup ( ... relationships => [ colors => { type => 'many to many', map_class => 'ProductColorMap' map_from => 'product', map_to => 'color', }, ... ], ); Note that only the map class needs to be Cd in the C class. The relationship definition specifies the name of the map class, and (optionally) the names of the foreign keys or "many to one" relationships in the map class that connect the two tables. In most cases, these two parameters (C and C) are unnecessary. L will figure out what to do given only the map class, so long as there's no ambiguity in the mapping table. In this case, there is no ambiguity, so the relationship definition can be shortened to this. use Product; ... __PACKAGE__->meta->setup ( relationships => [ colors => { type => 'many to many', map_class => 'ProductColorMap' }, ], ... ); In fact, since the map table is named according to the default L, it can be shortened even further. use Product; ... __PACKAGE__->meta->setup ( relationships => [ colors => { type => 'many to many' }, ... ], ... ); And further still: use Product; ... __PACKAGE__->meta->setup ( relationships => [ colors => 'many to many', ... ], ... ); (Classes can be shortened even more absurdly when auto-initialization is combined with the convention manager. See the L for an example.) Now let's revisit the example code. $p1 = Product->new(id => 10, name => 'Sled', colors => [ { name => 'red' }, { name => 'green' } ]); $p1->save; $p2 = Product->new(id => 20, name => 'Kite', colors => [ { name => 'blue' }, { name => 'green' }, { name => 'red' }, ]); $p2->save; The code works as expected, but the database now looks much nicer. mydb=# select * from colors; id | name ----+------- 1 | red 2 | green 3 | blue mydb=# select * from product_color_map; product_id | color_id ------------+---------- 10 | 1 10 | 2 20 | 3 20 | 2 20 | 1 Each color appears only once, and the mapping table handles all the connections between the C and C tables. The "many to many" C method works much like the "one to many" C method described earlier. The valid argument formats are the same. =over 4 =item * A list or reference to an array of objects of the appropriate class. =item * A list or reference to an array of hash references containing name/value pairs used to construct such objects. =item * A list or reference to an array of primary key values (but only if the foreign table has a single-column primary key). =back The database modification behavior is also the same, with changes happening when the "parent" object is saved. $p = Product->new(id => 123)->load; $p->colors({ name => 'green' }, { name => 'blue' }); $p->save; # database is modified here Setting the list of colors replaces the old list, but in the case of a "many to many" relationship, only the map records are deleted. $p = Product->new(id => 123)->load; $p->colors({ name => 'pink' }, { name => 'orange' }); # Delete old rows in the mapping table and create new ones $p->save; New colors can be added without deleting and resetting the entire list: # Add two colors to the existing list $p->add_colors({ name => 'gray' }, { name => 'red' }); $p->save; # database is modified here Passing a reference to an empty array will remove all colors associated with a particular product by deleting all the mapping table entries. $p->colors([]); $p->save; # all mapping table entries for this product deleted here Finally, the same caveats L about deleting products that have associated prices apply to colors as well. Again, I recommend using a trigger in the database to handle this, but L's cascading delete feature will work in a pinch. # Delete all associated rows in the prices table, plus any # rows in the product_color_map table, before deleting the # row in the products table. $p->delete(cascade => 1); =head3 Relationship code summary To summarize this exploration of inter-table relationships, here's a terse summary of the current state of our Perl classes, and the associated database tables. For the sake of brevity, I've chosen to use the shorter versions of the foreign key and relationship definitions in the Perl classes shown below. Just remember that this only works when your tables, columns, and classes are named according to the expected L. First, the database schema. CREATE TABLE vendors ( id SERIAL NOT NULL PRIMARY KEY, name VARCHAR(255) NOT NULL, UNIQUE(name) ); CREATE TABLE products ( id SERIAL NOT NULL PRIMARY KEY, name VARCHAR(255) NOT NULL, vendor_id INT REFERENCES vendors (id), status VARCHAR(128) NOT NULL DEFAULT 'inactive' CHECK(status IN ('inactive', 'active', 'defunct')), date_created TIMESTAMP NOT NULL DEFAULT NOW(), release_date TIMESTAMP, UNIQUE(name) ); CREATE TABLE prices ( id SERIAL NOT NULL PRIMARY KEY, product_id INT NOT NULL REFERENCES products (id), region CHAR(2) NOT NULL DEFAULT 'US', price DECIMAL(10,2) NOT NULL DEFAULT 0.00, UNIQUE(product_id, region) ); CREATE TABLE colors ( id SERIAL NOT NULL PRIMARY KEY, name VARCHAR(255) NOT NULL, UNIQUE(name) ); CREATE TABLE product_color_map ( product_id INT NOT NULL REFERENCES products (id), color_id INT NOT NULL REFERENCES colors (id), PRIMARY KEY(product_id, color_id) ); Now the Perl classes. Remember that these must each be in their own ".pm" files, despite appearing in one contiguous code snippet below. package Vendor; use base 'My::DB::Object'; __PACKAGE__->meta->setup ( table => 'vendors', columns => [ id => { type => 'serial', primary_key => 1, not_null => 1 }, name => { type => 'varchar', length => 255, not_null => 1 }, ], unique_key => 'name', ); 1; package Product; use base 'My::DB::Object'; __PACKAGE__->meta->setup ( table => 'products', columns => [ id => { type => 'integer', not_null => 1 }, name => { type => 'varchar', length => 255, not_null => 1 }, vendor_id => { type => 'int' }, status => { type => 'varchar', default => 'inactive', length => 128, not_null => 1 }, date_created => { type => 'timestamp', not_null => 1, default => 'now()' }, release_date => { type => 'timestamp' }, ] primary_key_columns => 'id', unique_key => 'name', allow_inline_column_values => 1, relationships => [ prices => 'one to many', colors => 'many to many', ] ); 1; package Price; use Product; use base 'My::DB::Object'; __PACKAGE__->meta->setup ( table => 'prices', columns => [ id => { type => 'serial', primary_key => 1, not_null => 1 }, product_id => { type => 'int', not_null => 1 }, region => { type => 'char', length => 2, not_null => 1 }, price => { type => 'decimal', precision => 10, scale => 2, not_null => 1, default => 0.00 }, ], unique_key => [ 'product_id', 'region' ], foreign_key => [ 'product' ], ); 1; package Color; use base 'My::DB::Object'; __PACKAGE__->meta->setup ( table => 'colors', columns => [ id => { type => 'serial', primary_key => 1, not_null => 1 }, name => { type => 'varchar', length => 255, not_null => 1 }, ], unique_key => 'name', ); 1; package ProductColorMap; use base 'My::DB::Object'; __PACKAGE__->meta->setup ( table => 'product_color_map', columns => [ product_id => { type => 'int', not_null => 1 }, color_id => { type => 'int', not_null => 1 }, ], pk_columns => [ 'product_id', 'color_id' ], foreign_keys => [ 'product', 'color' ], ); 1; =head2 The loader If the code above still looks like too much work to you, try letting L do it all for you. Given the database schema L, the suite of associated Perl classes could have been created automatically with a single method call. $loader = Rose::DB::Object::Loader->new(db => Rose::DB->new, class_prefix => 'My::'); $loader->make_classes; If you want to see what the loader did for you, catch the return value of the L method (which will be a list of class names) and then ask each class to print its perl equivalent. @classes = $loader->make_classes; foreach my $class (@classes) { if($class->isa('Rose::DB::Object')) { print $class->meta->perl_class_definition(braces => 'bsd', indent => 2), "\n"; } else # Rose::DB::Object::Manager subclasses { print $class->perl_class_definition, "\n"; } } You can also ask the loader to make actual Perl modules (that is, a set of actual *.pm files in the file system) by calling the aptly named L method. The code created by the loader is shown below. Compare it to the manually created Perl code L and you'll see that it's nearly identical. Again, careful table name choices really help here. Do what the L expects (or write your own convention manager subclass that does what I expect) and automation like this can work very well. package My::Color; use strict; use base qw(My::DB::Object::Base1); __PACKAGE__->meta->setup ( table => 'colors', columns => [ id => { type => 'integer', not_null => 1 }, name => { type => 'varchar', length => 255, not_null => 1 }, ], primary_key_columns => [ 'id' ], unique_keys => [ 'name' ], relationships => [ products => { column_map => { color_id => 'id' }, foreign_class => 'My::Product', map_class => 'My::ProductColorMap', map_from => 'color', map_to => 'product', type => 'many to many', }, ], ); 1; package My::Color::Manager; use base qw(Rose::DB::Object::Manager); use My::Color; sub object_class { 'My::Color' } __PACKAGE__->make_manager_methods('colors'); 1; package My::Price; use strict; use base qw(My::DB::Object::Base1); __PACKAGE__->meta->setup ( table => 'prices', columns => [ id => { type => 'integer', not_null => 1 }, product_id => { type => 'integer', not_null => 1 }, region => { type => 'character', default => 'US', length => 2, not_null => 1 }, price => { type => 'numeric', default => '0.00', not_null => 1, precision => 10, scale => 2 }, ], primary_key_columns => [ 'id' ], unique_key => [ 'product_id', 'region' ], foreign_keys => [ product => { class => 'My::Product', key_columns => { product_id => 'id', }, }, ], ); 1; package My::Price::Manager; use base qw(Rose::DB::Object::Manager); use My::Price; sub object_class { 'My::Price' } __PACKAGE__->make_manager_methods('prices'); 1; package My::ProductColorMap; use strict; use base qw(My::DB::Object::Base1); __PACKAGE__->meta->setup ( table => 'product_color_map', columns => [ product_id => { type => 'integer', not_null => 1 }, color_id => { type => 'integer', not_null => 1 }, ], primary_key_columns => [ 'product_id', 'color_id' ], foreign_keys => [ color => { class => 'My::Color', key_columns => { color_id => 'id', }, }, product => { class => 'My::Product', key_columns => { product_id => 'id', }, }, ], ); 1; package My::ProductColorMap::Manager; use base qw(Rose::DB::Object::Manager); use My::ProductColorMap; sub object_class { 'My::ProductColorMap' } __PACKAGE__->make_manager_methods('product_color_map'); 1; package My::ProductColor; use strict; use base qw(My::DB::Object::Base1); __PACKAGE__->meta->setup ( table => 'product_colors', columns => [ id => { type => 'integer', not_null => 1 }, product_id => { type => 'integer', not_null => 1 }, color_code => { type => 'character', length => 3, not_null => 1 }, ], primary_key_columns => [ 'id' ], ); 1; package My::ProductColor::Manager; use base qw(Rose::DB::Object::Manager); use My::ProductColor; sub object_class { 'My::ProductColor' } __PACKAGE__->make_manager_methods('product_colors'); 1; package My::Product; use strict; use base qw(My::DB::Object::Base1); __PACKAGE__->meta->setup ( table => 'products', columns => [ id => { type => 'integer', not_null => 1 }, name => { type => 'varchar', length => 255, not_null => 1 }, price => { type => 'numeric', default => '0.00', not_null => 1, precision => 10, scale => 2 }, vendor_id => { type => 'integer' }, status => { type => 'varchar', default => 'inactive', length => 128, not_null => 1 }, date_created => { type => 'timestamp', default => 'now()', not_null => 1 }, release_date => { type => 'timestamp' }, ], primary_key_columns => [ 'id' ], unique_keys => [ 'name' ], allow_inline_column_values => 1, foreign_keys => [ vendor => { class => 'My::Vendor', key_columns => { vendor_id => 'id', }, }, ], relationships => [ colors => { column_map => { product_id => 'id' }, foreign_class => 'My::Color', map_class => 'My::ProductColorMap', map_from => 'product', map_to => 'color', type => 'many to many', }, prices => { class => 'My::Price', key_columns => { id => 'product_id' }, type => 'one to many', }, ], ); 1; package My::Product::Manager; use base qw(Rose::DB::Object::Manager); use My::Product; sub object_class { 'My::Product' } __PACKAGE__->make_manager_methods('products'); 1; package My::Vendor; use strict; use base qw(My::DB::Object::Base1); __PACKAGE__->meta->setup ( table => 'vendors', columns => [ id => { type => 'integer', not_null => 1 }, name => { type => 'varchar', length => 255, not_null => 1 }, ], primary_key_columns => [ 'id' ], unique_keys => [ 'name' ], relationships => [ products => { class => 'My::Product', key_columns => { id => 'vendor_id' }, type => 'one to many', }, ], ); 1; package My::Vendor::Manager; use base qw(Rose::DB::Object::Manager); use My::Vendor; sub object_class { 'My::Vendor' } __PACKAGE__->make_manager_methods('vendors'); 1; =head2 Auto-joins and other Manager features The C class we created L is deceptively simple. Setting it up can actually be reduced to a one-liner, but it provides a rich set of features. The basics demonstrated earlier cover most kinds of single-table SELECT statements. But as the C class has become more complex, linking to other objects via L and other L, selecting rows from just the C table has become a lot less appealing. What good is it to retrieve hundreds of products in a single query when you then have to execute hundreds of individual queries to get the prices of those products? This is what SQL JOINs were made for: selecting related rows from multiple tables simultaneously. L supports a two kinds of joins. The interface to this functionality is presented in terms of objects via the C and C parameters to the L method. Both parameters expect a list of foreign key or relationship names. The C parameters will use an "inner join" to fetch related objects, while the C parameter will perform an "outer join." If you're unfamiliar with these terms, it's probably a good idea to learn about them from a good SQL book or web tutorial. But even if you've never written an SQL JOIN by hand, there's not much you need to understand in order to use your manager class effectively. The rule of thumb is simple. When you want each and every object returned by your query to have a particular related object, then use the C parameter. But if you do not want to exclude objects even if they do not have a particular related object attached to them yet, then use the C parameter. Sometimes, this decision is already made for you by the table structure. For example, let's modify the C table in order to require that every single product has a vendor. To do so, we'll change the C column definition from this: vendor_id INT REFERENCES vendors (id) to this: vendor_id INT NOT NULL REFERENCES vendors (id) Now it's impossible for a product to have a NULL C. And since our database enforces referential integrity, it's also impossible for the C column to have a value that does not refer to the C of an existing row in the C table. While the C parameter could technically be used to fetch Cs with their associated C objects, it would be wasteful. (Outer joins are often less efficient than inner joins.) The table structure basically dictates that the C parameter be used when fetching Cs with their Cs. Here's how such a query could actually look. $products = Product::Manager->get_products( query => [ name => { like => 'Kite%' }, id => { gt => 15 }, ] require_objects => [ 'vendor' ], sort_by => 'name'); Recall that the name of the foreign key that connects a product to its vendor is "vendor". Thus, the value of the C parameter is a reference to an array containing this name. Getting information about each product's vendor now no longer requires additional database queries. foreach my $product (@$products) { # This does not hit the database at all print $product->vendor->name, "\n"; } For the SQL-inclined, the actual query run looks something like this. SELECT t1.date_created, t1.id, t1.name, t1.release_date, t1.status, t1.vendor_id, t2.id, t2.name FROM products t1, vendors t2 WHERE t1.id >= 16 AND t1.name LIKE 'Kite%' AND t1.vendor_id = t2.id ORDER BY t1.name As you can see, the query includes "tN" aliases for each table. This is important because columns in separate tables often have identical names. For example, both the C and the C tables have columns named C and C. In the query, you'll notice that the C { like =E 'Kite%' }> argument ended up filtering on the product name rather than the vendor name. This is intentional. Any unqualified column name that is ambiguous is considered to belong to the "primary" table (C, in this case). The "tN" numbering is deterministic. The primary table is always "t1", and secondary tables are assigned ascending numbers starting from there. You can find a L of the numbering rules in the L documentation. In the example above, if we wanted to filter and sort on the vendor name instead, we could do this. $products = Product::Manager->get_products( query => [ 't2.name' => { like => 'Acm%' }, id => { gt => 15 }, ] require_objects => [ 'vendor' ], sort_by => 't2.name'); But that's not the only option. There are several ways to disambiguate a query clause. The column name can also be qualified by prefixing it with a relationship name. $products = Product::Manager->get_products( query => [ 'vendor.name' => { like => 'Acm%' }, id => { gt => 15 }, ] require_objects => [ 'vendor' ], sort_by => 'vendor.name'); The actual table name itself can also be used (although I do not recommend this practice since you will have to change all such usage instances if you ever rename the table). $products = Product::Manager->get_products( query => [ 'vendors.name' => { like => 'Acm%' }, id => { gt => 15 }, ] require_objects => [ 'vendor' ], sort_by => 'vendors.name'); Now let's see an example of the C parameter in action. Each C has zero or more Cs. Let's fetch products with all their associated prices. And remember that some of these products may have no prices at all. $products = Product::Manager->get_products( query => [ name => { like => 'Kite%' }, id => { gt => 15 }, ], with_objects => [ 'prices' ], sort_by => 'name'); Again, since the name of the "one to many" relationship that connects a product to its prices is "prices", this is the value use in the C parameter. The SQL looks something like this: SELECT t1.date_created, t1.id, t1.name, t1.release_date, t1.status, t1.vendor_id, t2.id, t2.price, t2.product_id, t2.region FROM products t1 LEFT OUTER JOIN prices t2 ON(t1.id = t2.product_id) WHERE t1.id > 15 AND t1.name LIKE 'Kite%' ORDER BY t1.name Fetching products with both their vendors and prices (if any) is straightforward. Just use the C parameter for the vendors and the C parameter for the prices. $products = Product::Manager->get_products( query => [ name => { like => 'Kite%' }, id => { gt => 15 }, ], require_objects => [ 'vendor' ], with_objects => [ 'prices' ], sort_by => 'name'); The resulting SQL is what you'd expect. SELECT t1.date_created, t1.id, t1.name, t1.release_date, t1.status, t1.vendor_id, t2.id, t2.price, t2.product_id, t2.region, t3.id, t3.name FROM products t1 JOIN vendors t3 ON (t1.vendor_id = t3.id) LEFT OUTER JOIN prices t2 ON(t1.id = t2.product_id) WHERE t1.id > 15 AND t1.name LIKE 'Kite%' ORDER BY t1.name Each C also has zero or more Cs which are related to it through a mapping table (fronted by the C class, but we don't need to know that). The C parameter can handle that as well. $products = Product::Manager->get_products( query => [ name => { like => 'Kite%' }, id => { gt => 15 }, ], with_objects => [ 'colors' ], sort_by => 'name'); The resulting SQL is a bit more complex. SELECT t1.date_created, t1.id, t1.name, t1.release_date, t1.status, t1.vendor_id, t3.id, t3.name FROM products t1 LEFT OUTER JOIN product_color_map t2 ON(t2.product_id = t1.id) LEFT OUTER JOIN colors t3 ON(t2.color_id = t3.id) WHERE t1.id > 15 AND t1.name LIKE 'Kite%' Again, combinations are straightforward. Let's fetch products with their vendors and colors. $products = Product::Manager->get_products( query => [ name => { like => 'Kite%' }, id => { gt => 15 }, ], require_objects => [ 'vendor' ], with_objects => [ 'colors' ], sort_by => 'name'); Now the SQL is starting to get a bit hairy. SELECT t1.id, t1.name, t1.vendor_id, t3.code, t3.name, t4.id, t4.name, t4.region_id FROM products t1 JOIN vendors t4 ON (t1.vendor_id = t4.id) LEFT OUTER JOIN product_colors t2 ON (t2.product_id = t1.id) LEFT OUTER JOIN colors t3 ON (t2.color_code = t3.code) WHERE t1.id > 15 AND t1.name LIKE 'Kite%' Anyone who knows SQL well will recognize that there is a danger lurking when combining JOINs. Multiple joins that each fetch multiple rows can result in a geometric explosion of rows returned by the database. For example, the number of rows returned when fetching products with their associated prices and colors would be: x x That number can get very large, very fast if products have many prices, colors, or both. (The last two terms in the multiplication maybe switched, depending on the order of the actual JOIN clauses, but the results are similar.) And the problem only gets worse as the number of objects related by "... to many" relationships increases. That said, L does allow multiple objects related by "... to many" relationships to be fetched simultaneously. But it requires the developer to supply the C parameter with a true value as a form of confirmation. "Yes, I know the risks, but I want to do it anyway." As an example, let's try fetching products with their associated prices, colors, and vendors. To do so, we'll have to include the C parameter. $products = Product::Manager->get_products( query => [ name => { like => 'Kite%' }, id => { gt => 15 }, ], require_objects => [ 'vendor' ], with_objects => [ 'colors', 'prices' ], multi_many_ok => 1, sort_by => 'name'); Here's the SQL. SELECT t1.id, t1.name, t1.vendor_id, t3.code, t3.name, t4.price_id, t4.product_id, t4.region, t4.price, t5.id, t5.name, t5.region_id FROM products t1 JOIN vendors t5 ON (t1.vendor_id = t5.id) LEFT OUTER JOIN product_colors t2 ON (t2.product_id = t1.id) LEFT OUTER JOIN colors t3 ON (t2.color_code = t3.code) LEFT OUTER JOIN prices t4 ON (t1.id = t4.product_id) WHERE t1.id > 15 AND t1.name LIKE 'Kite%' ORDER BY t1.name It's questionable whether this five-way join will be faster than doing a four- or three-way join and then fetching the other information after the fact, with separate queries. It all depends on the number of rows expected to match. Only you know your data. You must choose the most efficient query that suits your needs. Moving beyond even the example above, it's possible to chain foreign key or relationship names to an arbitrary depth. For example, imagine that each C has a C related to it by a foreign key named "region". The following call will get region information for each product's vendor, filtering on the region name. $products = Product::Manager->get_products( query => [ 'vendor.region.name' => 'UK', 'name' => { like => 'Kite%' }, 'id' => { gt => 15 }, ], require_objects => [ 'vendor.region' ], with_objects => [ 'colors', 'prices' ], multi_many_ok => 1, sort_by => 'name'); The SQL would now look something like this. SELECT t1.id, t1.name, t1.vendor_id, t3.code, t3.name, t4.price_id, t4.product_id, t4.region, t4.price, t5.id, t5.name, t5.region_id, t6.id, t6.name FROM products t1 JOIN (vendors t5 JOIN regions t6 ON (t5.region_id = t6.id)) ON (t1.vendor_id = t5.id) LEFT OUTER JOIN product_colors t2 ON (t2.product_id = t1.id) LEFT OUTER JOIN colors t3 ON (t2.color_code = t3.code) LEFT OUTER JOIN prices t4 ON (t1.id = t4.product_id) WHERE t1.id > 15 AND t1.name LIKE 'Kite%' AND t6.name = 'UK' ORDER BY t1.name The same caveat about performance and the potential explosion of redundant data when JOINing across multiple "... to many" relationships also applies to the "chained" selectors demonstrated above--even more so, in fact, as the depth of the chain increases. That said, it's usually safe to go a few levels deep into "... to one" relationships when using the C parameter. Finally, it's also possible to load a single product with all of its associated foreign objects. The L method accepts a C parameter that takes a list of foreign key and relationship names. $product = Product->new(id => 1234); $product->load(with => [ 'vendor', 'colors', 'prices' ]); The same "multi many" caveats apply, but the C parameter is not required in this case. The assumption is that a single object won't have too many related objects. But again, only you know your data, so be careful. =head2 Wrap-up I hope you've learned something from this tutorial. Although it is by no means a complete tour of all of the features of L, it does hit most of the highlights. This tutorial will likely expand in the future, and a separate document describing the various ways that L can be extended is also planned. For now, there is a brief overview that was pulled from the L mailing list in the wiki. https://github.com/siracusa/rose/wiki/Extending-Rose%3A%3ADB%3A%3AObject See the L section below for more information on the mailing list. =head1 DEVELOPMENT POLICY The L applies to this, and all C modules. Please install L from CPAN and then run "C" for more information. =head1 SUPPORT Any L questions or problems can be posted to the L mailing list. To subscribe to the list or view the archives, go here: L Although the mailing list is the preferred support mechanism, you can also email the author (see below) or file bugs using the CPAN bug tracking system: L There's also a wiki and other resources linked from the Rose project home page: L =head1 AUTHOR John C. Siracusa (siracusa@gmail.com) =head1 COPYRIGHT Copyright (c) 2007 by John C. Siracusa. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Rose-DB-Object-0.820/lib/Rose/DB/Object/ConventionManager.pm000755 000765 000024 00000161234 12502134373 023361 0ustar00johnstaff000000 000000 package Rose::DB::Object::ConventionManager; use strict; use Carp(); use Scalar::Util(); use Rose::DB::Object::Metadata::ForeignKey; use Rose::DB::Object::Metadata::Object; our @ISA = qw(Rose::DB::Object::Metadata::Object); our $VERSION = '0.795'; our $Debug = 0; use Rose::Object::MakeMethods::Generic ( 'scalar --get_set_init' => [ 'singular_to_plural_function', 'plural_to_singular_function', ], boolean => [ tables_are_singular => { default => 0 }, force_lowercase => { default => 0 }, no_auto_sequences => { default => 0 }, ], ); *meta = \&Rose::DB::Object::Metadata::Object::parent; sub class_to_table_singular { my($self, $class) = @_; $class ||= $self->meta->class; my $table = $self->class_suffix($class); $table =~ s/([a-z]\d*|^\d+)([A-Z])/$1_$2/g; return lc $table; } sub class_suffix { my($self, $class) = @_; $class =~ /(\w+)$/; return $1; } sub class_to_table_plural { my($self) = shift; $self->singular_to_plural($self->class_to_table_singular(@_)); } sub table_to_class_plural { my($self, $table, $prefix) = @_; return $self->table_to_class($table, $prefix, 1); } sub table_to_class { my($self, $table, $prefix, $plural) = @_; $table = lc $table if ($self->force_lowercase); $table = $self->plural_to_singular($table) unless($plural); $table =~ s/_(.)/\U$1/g; $table =~ s/[^\w:]/_/g; return ($prefix || '') . ucfirst $table; } sub auto_manager_base_name { my($self, $table, $object_class) = @_; $table ||= $self->class_to_table_plural; $table = lc $table if($self->force_lowercase); return $self->tables_are_singular ? $self->singular_to_plural($table) : $table; } sub auto_manager_base_class { 'Rose::DB::Object::Manager' } sub auto_manager_class_name { my($self, $object_class) = @_; $object_class ||= $self->meta->class; return "${object_class}::Manager"; } sub auto_manager_method_name { my($self, $type, $base_name, $object_class) = @_; return undef; # rely on hard-coded defaults in Manager } sub class_prefix { my($self, $class) = @_; $class =~ /^((?:\w+::)*)/; return $1 || ''; } sub related_table_to_class { my($self, $table, $local_class, $plural) = @_; return $self->table_to_class($table, $self->class_prefix($local_class), $plural); } sub table_singular { my($self) = shift; my $table = $self->meta->table; if($self->tables_are_singular) { return $table; } return $self->plural_to_singular($table); } sub table_plural { my($self) = shift; my $table = $self->meta->table; if($self->tables_are_singular) { return $self->singular_to_plural($table); } return $table; } sub auto_table_name { my($self) = shift; if($self->tables_are_singular) { return $self->class_to_table_singular; } else { return $self->class_to_table_plural; } } sub auto_primary_key_column_names { my($self) = shift; my $meta = $self->meta; # 1. Column named "id" return [ 'id' ] if($meta->column('id')); # 2. Column named _id my $column = $self->class_to_table_singular . '_id'; return [ $column ] if($meta->column($column)); # 3. The first serial column in the column list, alphabetically foreach my $column (sort { lc $a->name cmp lc $b->name } $meta->columns) { return [ $column->name ] if($column->type =~ /^(?:big)?serial$/); } # 4. The first column if(my $column = $meta->first_column) { return [ $column->name ]; } return; } sub auto_column_method_name { my($self, $type, $column, $name, $object_class) = @_; return lc $name if ($self->force_lowercase); return undef; # rely on hard-coded defaults in Metadata } sub init_singular_to_plural_function { } sub init_plural_to_singular_function { } sub singular_to_plural { my($self, $word) = @_; if(my $code = $self->singular_to_plural_function) { return $code->($word); } if($word =~ /(?:x|[se]s)$/i) { return $word . 'es'; } else { $word =~ s/y$/ies/i; } return $word =~ /s$/i ? $word : ($word . 's'); } sub plural_to_singular { my($self, $word) = @_; if(my $code = $self->plural_to_singular_function) { return $code->($word); } $word =~ s/ies$/y/i; return $word if($word =~ s/ses$/s/i); return $word if($word =~ /[aeiouy]ss$/i); $word =~ s/s$//i; return $word; } sub method_name_conflicts { my($self, $name) = @_; return 1 if(Rose::DB::Object->can($name)); my $meta = $self->meta; foreach my $column ($meta->columns) { foreach my $type ($column->auto_method_types) { my $method = $column->method_name($type) || $meta->method_name_from_column_name($column->name, $type) || next; return 1 if($name eq $method); } } foreach my $foreign_key ($meta->foreign_keys) { foreach my $type ($foreign_key->auto_method_types) { my $method = $foreign_key->method_name($type) || $foreign_key->build_method_name_for_type($type) || next; return 1 if($name eq $method); } } foreach my $relationship ($meta->relationships) { foreach my $type ($relationship->auto_method_types) { my $method = $relationship->method_name($type) || $relationship->build_method_name_for_type($type) || next; return 1 if($name eq $method); } } return 0; } sub auto_column_sequence_name { my($self, $table, $column, $db) = @_; my $name = join('_', $table, $column, 'seq'); return uc $name if($db && $db->likes_uppercase_sequence_names); return lc $name if($db && $db->likes_lowercase_sequence_names); return $name; } sub auto_primary_key_column_sequence_name { shift->auto_column_sequence_name(@_) } sub auto_foreign_key_name { my($self, $f_class, $current_name, $key_columns, $used_names) = @_; if($self->force_lowercase) { $current_name = lc $current_name; $key_columns = { map { lc } %$key_columns }; } my $f_meta = $f_class->meta or return $current_name; my $name = $self->plural_to_singular($f_meta->table) || $current_name; if(keys %$key_columns == 1) { my($local_column, $foreign_column) = %$key_columns; # Try to lop off foreign column name. Example: # my_foreign_object_id -> my_foreign_object if($local_column =~ s/_$foreign_column$//i) { $name = $local_column; } else { $name = $self->plural_to_singular($f_meta->table) || $current_name; } } # Avoid method name conflicts if($self->method_name_conflicts($name) || $used_names->{$name}) { foreach my $s ('_obj', '_object') { # Try the name with a suffix appended unless($self->method_name_conflicts($name . $s) || $used_names->{$name . $s}) { return $name . $s; } } my $i = 1; # Give up and go with numbers... $i++ while($self->method_name_conflicts($name . $i) || $used_names->{$name . $i}); return $name . $i; } return $name; } sub auto_table_to_relationship_name_plural { my($self, $table) = @_; $table = lc $table if ($self->force_lowercase); return $self->tables_are_singular ? $self->singular_to_plural($table) : $table; } sub auto_class_to_relationship_name_plural { my($self, $class) = @_; return $self->class_to_table_plural($class); } sub auto_foreign_key_to_relationship_name_plural { my($self, $fk) = @_; my $name = $self->force_lowercase ? lc $fk->name : $fk->name; return $self->singular_to_plural($name); } sub auto_relationship_name_one_to_many { my($self, $table, $class) = @_; #return $self->auto_class_to_relationship_name_plural($class); my $name = $self->auto_table_to_relationship_name_plural($table); # Avoid method name conflicts if($self->method_name_conflicts($name)) { foreach my $s ('_objs', '_objects') { # Try the name with a suffix appended unless($self->method_name_conflicts($name . $s)) { return $name . $s; } } my $i = 1; # Give up and go with numbers... $i++ while($self->method_name_conflicts($name . $i)); return $name . $i; } return $name; } sub auto_relationship_name_many_to_many { my($self, $fk, $map_class) = @_; my $name = $self->auto_foreign_key_to_relationship_name_plural($fk); # Avoid method name conflicts if($self->method_name_conflicts($name)) { foreach my $s ('_objs', '_objects') { # Try the name with a suffix appended unless($self->method_name_conflicts($name . $s)) { return $name . $s; } } my $i = 1; # Give up and go with numbers... $i++ while($self->method_name_conflicts($name . $i)); return $name . $i; } return $name; } sub auto_relationship_name_one_to_one { my($self, $table, $class) = @_; $table = lc $table if ($self->force_lowercase); my $name = $self->plural_to_singular($table); # Avoid method name conflicts if($self->method_name_conflicts($name)) { foreach my $s ('_obj', '_object') { # Try the name with a suffix appended unless($self->method_name_conflicts($name . $s)) { return $name . $s; } } my $i = 1; # Give up and go with numbers... $i++ while($self->method_name_conflicts($name . $i)); return $name . $i; } return $name; } sub is_map_class { my($self, $class) = @_; return 0 unless(UNIVERSAL::isa($class, 'Rose::DB::Object')); my $is_map_table = $self->looks_like_map_table($class->meta->table); my $is_map_class = $self->looks_like_map_class($class); return 1 if($is_map_table && (!defined $is_map_class || $is_map_class)); return 0; } sub looks_like_map_class { my($self, $class) = @_; unless(UNIVERSAL::isa($class, 'Rose::DB::Object')) { return undef; } my $meta = $class->meta; my @fks = $meta->foreign_keys; return 1 if(@fks == 2); return 0 if(($meta->is_initialized || $meta->initialized_foreign_keys) && !$meta->has_deferred_foreign_keys); return undef; } sub looks_like_map_table { my($self, $table) = @_; if($table =~ m{^(?: (?:\w+_){2,}map # foo_bar_map | (?:\w+_)*\w+_(?:\w+_)*\w+s # foo_bars | (?:\w+_)*\w+s_(?:\w+_)*\w+s # foos_bars )$}xi) { return 1; } return 0; } sub auto_foreign_key { my($self, $name, $spec) = @_; $spec ||= {}; my $meta = $self->meta; unless($spec->{'class'}) { my $class = $meta->class; my $fk_class = $self->related_table_to_class($name, $class); LOAD: { # Try to load class no strict 'refs'; unless(UNIVERSAL::isa($fk_class, 'Rose::DB::Object')) { local $@; eval "require $fk_class"; return if($@ || !UNIVERSAL::isa($fk_class, 'Rose::DB::Object')); } } #return unless(UNIVERSAL::isa($fk_class, 'Rose::DB::Object')); $spec->{'class'} = $fk_class; } unless(defined $spec->{'key_columns'}) { my @fpk_columns = UNIVERSAL::isa($spec->{'class'}, 'Rose::DB::Object') ? $spec->{'class'}->meta->primary_key_column_names : (); # Defer population of key columns until the foreign class is initialized unless(@fpk_columns == 1) { # If the foreign class has more than one primary key column, give up return if(@fpk_columns); # If the foreign class is initialized and the foreign key spec still # has no key columns, then give up. if(UNIVERSAL::isa($spec->{'class'}, 'Rose::DB::Object') && $spec->{'class'}->meta->is_initialized) { return; } my %spec = %$spec; $meta->add_deferred_task( { class => $meta->class, method => "foreign_key:$name", code => sub { # Generate new foreign key, then grab the key columns from it my $new_fk = $self->auto_foreign_key($name, \%spec) or return; my $fk = $meta->foreign_key($name); my $key_cols = $new_fk->key_columns or return; $fk->key_columns($key_cols); }, check => sub { my $fk = $meta->foreign_key($name) or return 0; # If the foreign class is initialized and the foreign key still # has no key columns, then we should give up. if(UNIVERSAL::isa($fk->class, 'Rose::DB::Object') && $fk->class->meta->is_initialized) { Carp::croak "Missing key columns for foreign key named ", $fk->name, " in class ", $meta->class; } my $cols = $fk->key_columns or return 0; # Everything is okay if we have key columns return (ref($cols) && keys(%$cols) > 0) ? 1 : 0; } }); return Rose::DB::Object::Metadata::ForeignKey->new(name => $name, %$spec); } my $aliases = $meta->column_aliases; if($meta->column($name) && $aliases->{$name} && $aliases->{$name} ne $name) { $spec->{'key_columns'} = { $name => $fpk_columns[0] }; } elsif($meta->column("${name}_$fpk_columns[0]")) { $spec->{'key_columns'} = { "${name}_$fpk_columns[0]" => $fpk_columns[0] }; } else { return } } return Rose::DB::Object::Metadata::ForeignKey->new(name => $name, %$spec); } sub auto_relationship { my($self, $name, $rel_class, $spec) = @_; $spec ||= {}; my $meta = $self->meta; my $rel_type = $rel_class->type; unless($spec->{'class'}) { if($rel_type eq 'one to many') { my $class = $meta->class; # Get class suffix from relationship name my $table = $self->plural_to_singular($name); my $f_class = $self->related_table_to_class($table, $class); LOAD: { # Try to load class no strict 'refs'; unless(UNIVERSAL::isa($f_class, 'Rose::DB::Object')) { local $@; eval "require $f_class"; return if($@ || !UNIVERSAL::isa($f_class, 'Rose::DB::Object')); } } #return unless(UNIVERSAL::isa($f_class, 'Rose::DB::Object')); $spec->{'class'} = $f_class; } elsif($rel_type =~ /^(?:one|many) to one$/) { my $class = $meta->class; # Get class suffix from relationship name my $f_class = $self->related_table_to_class($name, $class); LOAD: { # Try to load class no strict 'refs'; unless(UNIVERSAL::isa($f_class, 'Rose::DB::Object')) { local $@; eval "require $f_class"; return if($@ || !UNIVERSAL::isa($f_class, 'Rose::DB::Object')); } } #return unless(UNIVERSAL::isa($f_class, 'Rose::DB::Object')); $spec->{'class'} = $f_class; } } # Make sure this class has its @ISA set up... unless(UNIVERSAL::isa($spec->{'class'}, 'Rose::DB::Object')) { # ...but allow many-to-many relationships to pass because they tend to # need more time before every piece of info is available. return unless($rel_type eq 'many to many'); } if($rel_type eq 'one to one') { return $self->auto_relationship_one_to_one($name, $rel_class, $spec); } elsif($rel_type eq 'many to one') { return $self->auto_relationship_many_to_one($name, $rel_class, $spec); } elsif($rel_type eq 'one to many') { return $self->auto_relationship_one_to_many($name, $rel_class, $spec); } elsif($rel_type eq 'many to many') { return $self->auto_relationship_many_to_many($name, $rel_class, $spec); } return; } sub auto_relationship_one_to_one { my($self, $name, $rel_class, $spec) = @_; $spec ||= {}; my $meta = $self->meta; unless(defined $spec->{'column_map'}) { my @fpk_columns = $spec->{'class'}->meta->primary_key_column_names; return unless(@fpk_columns == 1); my $aliases = $meta->column_aliases; if($meta->column($name) && $aliases->{$name} && $aliases->{$name} ne $name) { $spec->{'column_map'} = { $name => $fpk_columns[0] }; } elsif($meta->column("${name}_$fpk_columns[0]")) { $spec->{'column_map'} = { "${name}_$fpk_columns[0]" => $fpk_columns[0] }; } elsif($meta->column("${name}_id")) { $spec->{'column_map'} = { "${name}_id" => $fpk_columns[0] }; } else { return } } return $rel_class->new(name => $name, %$spec); } *auto_relationship_many_to_one = \&auto_relationship_one_to_one; sub auto_relationship_one_to_many { my($self, $name, $rel_class, $spec) = @_; $spec ||= {}; my $meta = $self->meta; my $l_col_name = $self->class_to_table_singular; unless(defined $spec->{'column_map'}) { my @pk_columns = $meta->primary_key_column_names; return unless(@pk_columns == 1); my @fpk_columns = $meta->primary_key_column_names; return unless(@fpk_columns == 1); my $f_meta = $spec->{'class'}->meta; my $aliases = $f_meta->column_aliases; if($f_meta->column($l_col_name)) { $spec->{'column_map'} = { $pk_columns[0] => $l_col_name }; } elsif($f_meta->column("${l_col_name}_$pk_columns[0]")) { $spec->{'column_map'} = { $pk_columns[0] => "${l_col_name}_$pk_columns[0]" }; } else { return } } return $rel_class->new(name => $name, %$spec); } sub auto_relationship_many_to_many { my($self, $name, $rel_class, $spec) = @_; $spec ||= {}; my $meta = $self->meta; unless($spec->{'map_class'}) { my $class = $meta->class; # Given: # Class: My::Object # Rel name: other_objects # Foreign class: My::OtherObject # # Consider map class names: # My::ObjectsOtherObjectsMap # My::ObjectOtherObjectMap # My::OtherObjectsObjectsMap # My::OtherObjectObjectMap # My::ObjectsOtherObjects # My::ObjectOtherObjects # My::OtherObjectsObjects # My::OtherObjectObjects # My::OtherObjectMap # My::OtherObjectsMap # My::ObjectMap # My::ObjectsMap my $prefix = $self->class_prefix($class); my @consider; my $f_class_suffix = $self->table_to_class($name); my $f_class_suffix_pl = $self->table_to_class_plural($name); $class =~ /(\w+)$/; my $class_suffix = $1; my $class_suffix_pl = $self->singular_to_plural($class_suffix); push(@consider, map { "${prefix}$_" } $class_suffix_pl . $f_class_suffix_pl . 'Map', $class_suffix . $f_class_suffix . 'Map', $f_class_suffix_pl . $class_suffix_pl . 'Map', $f_class_suffix . $class_suffix . 'Map', $class_suffix_pl . $f_class_suffix_pl, $class_suffix . $f_class_suffix_pl, $f_class_suffix_pl . $class_suffix_pl, $f_class_suffix . $class_suffix_pl, $f_class_suffix . 'Map', $f_class_suffix_pl . 'Map', $class_suffix . 'Map', $class_suffix_pl . 'Map'); my $map_class; CLASS: foreach my $class (@consider) { LOAD: { # Try to load class no strict 'refs'; if(UNIVERSAL::isa($class, 'Rose::DB::Object')) { $map_class = $class; last CLASS; } else { local $@; eval "require $class"; unless($@) { $map_class = $class; last CLASS if(UNIVERSAL::isa($class, 'Rose::DB::Object')); } } } } return unless($map_class && UNIVERSAL::isa($map_class, 'Rose::DB::Object')); $spec->{'map_class'} = $map_class; } return $rel_class->new(name => $name, %$spec); } 1; __END__ =head1 NAME Rose::DB::Object::ConventionManager - Provide missing metadata by convention. =head1 SYNOPSIS package My::Product; use base 'Rose::DB::Object'; __PACKAGE__->meta->setup(columns => [ ... ]); # No table is set above, but look at this: the # convention manager provided one for us. print __PACKAGE__->meta->table; # "products" ## ## See the EXAMPLE section below for a more complete demonstration. ## =head1 DESCRIPTION Each L-derived object has a L that it uses to fill in missing L. The convention manager encapsulates a set of rules (conventions) for generating various pieces of metadata in the absence of explicitly specified values: table names, column names, etc. Each L-derived class's convention manager object is stored in the L attribute of its L (L) object. L is the default convention manager class. The object method documentation below describes both the purpose of each convention manager method and the particular rules that L follows to fulfill that purpose. Subclasses must honor the purpose of each method, but are free to use any rules they choose. B When reading the descriptions of the rules used by each convention manager method below, remember that only values that are I will be set by the convention manager. Explicitly providing a value for a piece of metadata obviates the need for the convention manager to generate one. If insufficient information is available, or if the convention manager simply declines to fulfill a request, undef may be returned from any metadata-generating method. In the documentation, the adjectives "local" and "foreign" are used to distinguish between the things that belong to the convention manager's L and the class on "the other side" of the inter-table relationship, respectively. =head1 SUMMARY OF DEFAULT CONVENTIONS Although the object method documentation below includes all the information required to understand the default conventions, it's also quite spread out. What follows is a summary of the default conventions. Some details have necessarily been omitted or simplified for the sake of brevity, but this summary should give you a good starting point for further exploration. Here's a brief summary of the default conventions as implemented in L. =over 4 =item B Examples: C, C, C, C. =item B
Examples: C, C, C, C, C. (This convention can be overridden via the L method.) =item B Examples: C, C, C, C, C. =item B For example, the primary key column name in the C table might be C or C, but should B be C or C. =item B Examples: C, C, C. =item B Examples: C, C, C. These relationships may point to zero or one foreign object. The default method names generated from such relationships are based on the relationship names, so singular names make the most sense. =item B Examples: C, C, C. These relationships may point to more than one foreign object. The default method names generated from such relationships are based on the relationship names, so plural names make the most sense. =item B See the L, L, and L documentation for all the details. =back =head1 CONSTRUCTOR =over 4 =item B Constructs a new object based on PARAMS, where PARAMS are name/value pairs. Any object attribute is a valid parameter name. =back =head1 OBJECT METHODS =over 4 =item B Given a L column L, a L object or column name, a default method name, and a L-derived class name, return an appropriate method name. The default implementation simply returns undef, relying on the hard-coded default method-type-to-name mapping implemented in L's L method. =item B Given a L name and an optional reference to a hash SPEC of the type passed to L's L method, return an appropriately constructed L object. The foreign key's L is generated by calling L, passing NAME and the convention manager's L as arguments. An attempt is made is load the class. If this fails, the foreign key's L is not set. The foreign key's L are only set if both the "local" and "foreign" tables have single-column primary keys. The foreign class's primary key column name is used as the foreign column in the L map. If there is a local column with the same name as the foreign key name, and if that column is aliased (making way for the foreign key method to use that name), then that is used as the local column. If not, then the local column name is generated by joining the foreign key name and the foreign class's primary key column name with an underscore. If no column by that name exists, then the search is abandoned. Example: Given these pieces: Name Description Value --------- -------------------------------- ------- NAME Foreign key name vendor FCLASS Foreign class My::Vendor FPK Foreign primary key column name id Consider column maps in this order: Value Formula --------------------- ---------------------- { vendor => 'id' } { NAME => FPK } { vendor_id => 'id' } { _ => FPK } =item B Given the name of a foreign class, the current foreign key name (if any), a reference to a hash of L, and a reference to a hash whose keys are foreign key names already used in this class, return a L for the foreign key. If there is more than one pair of columns in KEY_COLUMNS, then the name is generated by calling L, passing the L name of the foreign class. The CURRENT_NAME is used if the call to L does not return a true value. If there is just one pair of columns in KEY_COLUMNS, and if the name of the local column ends with an underscore and the name of the referenced column, then that part of the column name is removed and the remaining string is used as the foreign key name. For example, given the following tables: CREATE TABLE categories ( id SERIAL PRIMARY KEY, ... ); CREATE TABLE products ( category_id INT REFERENCES categories (id), ... ); The foreign key name would be "category", which is the name of the referring column ("category_id") with an underscore and the name of the referenced column ("_id") removed from the end of it. If the foreign key has only one column, but it does not meet the criteria described above, then the name is generated by calling L, passing the L name of the foreign class. The CURRENT_NAME is used if the call to L does not return a true value. If the name selected using the above techniques is in the USED_NAMES hash, or is the same as that of an existing or potential method in the target class, then the suffixes "_obj" and "_object" are tried in that order. If neither of those suffixes resolves the situation, then ascending numeric suffixes starting with "1" are tried until a unique name is found. =item B Given a table name and the name of the L-derived class that fronts it, return a base name suitable for use as the value of the C parameter to L's L method. If no table is specified then the table name is derived from the current class name by calling L. If L is true, then TABLE is passed to the L method and the result is returned. Otherwise, TABLE is returned as-is. =item B Return the class that all manager classes will default to inheriting from. By default this will be L. =item B Given the name of a L-derived class, returns a class name for a L-derived class to manage such objects. The default implementation simply appends "::Manager" to the L-derived class name. =item B Given the specified L L, L, and L return an appropriate L method name. The default implementation simply returns undef, relying on the hard-coded default method-type-to-name mapping implemented in L's L method. =item B Return the name of a "many to many" relationship that fetches objects from the table pointed to by the L object FK by going through the class MAPCLASS. The default implementation passes the name of the table pointed to by FK through the L method in order to build the name. If the selected name is the name of an existing or potential method in the target class, then the suffixes "_objs" and "_objects" are tried in that order. If neither of those suffixes resolves the situation, then ascending numeric suffixes starting with "1" are tried until a unique name is found. =item B Return the name of a "one to many" relationship that fetches objects from the specified TABLE and CLASS. If L is true, then TABLE is passed to the L method and the result is used as the name. Otherwise, TABLE is used as-is. If the selected name is the name of an existing or potential method in the target class, then the suffixes "_objs" and "_objects" are tried in that order. If neither of those suffixes resolves the situation, then ascending numeric suffixes starting with "1" are tried until a unique name is found. =item B Return the name of a "one to one" relationship that fetches an object from the specified TABLE and CLASS. The default implementation returns a singular version of the table name. If the selected name is the name of an existing or potential method in the target class, then the suffixes "obj_" and "_object" are tried in that order. If neither of those suffixes resolves the situation, then ascending numeric suffixes starting with "1" are tried until a unique name is found. =item B Returns a reference to an array of primary key column names. If a column named "id" exists, it is selected as the sole primary key column name. If not, the column name generated by joining the return value of L with "_id" is considered. If no column with that name exists, then the first column (sorted alphabetically) whose L is "serial" is selected. If all of the above fails, then the L is selected as the primary key column (assuming one exists). Examples: My::A->meta->columns(qw(a a_id id)); print My::A->meta->primary_key_columns; # "id" My::B->meta->columns(qw(b b_id foo)); print My::B->meta->primary_key_columns; # "a_id" My::D->meta->columns ( cnt => { type => 'int' }, dub => { type => 'serial' }, foo => { type => 'serial'}, a_id => { type => 'int' } ) print My::D->meta->primary_key_columns; # "dub" My::C->meta->columns(qw(foo bar baz)); print My::C->meta->primary_key_columns; # "foo" =item B Given a L name, a L-derived class name, and an optional reference to a hash SPEC of the type passed to L's L method, return an appropriately constructed L-derived object. If the relationship's L is "one to one" or "many to one", then the relationship's L is generated by calling L, passing NAME and the convention manager's L as arguments. An attempt is made is load the class. If this fails, the relationship's L is not set. The L for "one to one" and "many to one" relationships is generated using the same rules used to generate L in the L method. If the relationship's L is "one to many" then the relationship's L is generated by calling L on NAME, then passing that value along with the convention manager's L to the L method. An attempt is made is load the class. If this fails, the relationship's L is not set. The L for a "one to many" relationship is only set if both the "local" and "foreign" tables have single-column primary keys. The following ordered list of combinations is considered. Given: Local class: My::Product Foreign class: My::Price Relationship: prices Generate these pieces: Name Description Value --------- --------------------------------- ------- LTABLE_S Local class_to_table_singular() product LPK Local primary key column name id FPK Foreign primary key column name id Consider column maps in this order: Value Formula ---------------------- -------------------------- { id => 'product' } { LPK => LTABLE_S } { id => 'product_id' } { LPK => _ } The first value whose foreign column actually exists in the foreign table is chosen. If the relationship's L is "many to many" then the relationship's L is chosen from a list of possibilities. This list is generated by constructing singular and plural versions of the local and foreign class names (sans prefixes) and then joining them in various ways, all re-prefixed by the L of the convention manager's L. Example: Given: Local class: My::Product Foreign class: My::Color Relationship: colors Generate these pieces: Name Description Value --------- --------------------------------- ------- PREFIX Local class prefix My:: LCLASS_S Unprefixed local class, singular Product LCLASS_P Unprefixed local class, plural Products FCLASS_S Unprefixed foreign class, singular Color FCLASS_P Unprefixed foreign class, plural Colors Consider map class names in this order: Value Formula --------------- --------------------- My::ProductsColorsMap Map My::ProductColorMap Map My::ColorsProductsMap Map My::ColorProductMap Map My::ProductsColors My::ProductColors My::ColorsProducts My::ColorProducts My::ColorMap Map My::ColorsMap Map My::ProductMap Map My::ProductsMap Map The first class found that inherits from L and is loaded successfully will be chosen as the relationship's L. =item B Returns a table name for the convention manager's L. Class names are singular and table names are plural. To build the table name, the L is removed from the L, transitions from lowercase letters or digits to uppercase letters have underscores inserted, and the whole thing is converted to lowercase. Examples: Class Table ----------- -------- Product products My::Product products My::BigBox big_boxes My5HatPig my5_hat_pig =item B Get or set the L-derived class that this convention manager belongs to. =item B Given a class name, return the prefix, if any, before the last component of the namespace, including the final "::". If there is no prefix, an empty string is returned. Examples: Class Prefix ----------- -------------- Product My::Product My:: A::B::C::D A::B::C:: =item B Given a class name, or the convention manager's L if omitted, return a plural version of the corresponding table name. To do this, the output of the L method is passed to a call to the L method. (The CLASS argument, if any, is passed to the call to L.) Examples: Class Table ----------- -------- Product products My::Product products My::Box boxes =item B Given a class name, or the convention manager's L if omitted, return a singular version of the corresponding table name. Examples: Class Table ----------- -------- Product product My::Product product My::Box box =item B Get or set a boolean value that indicates whether or not L entity names should be forced to lowercase even when the related entity is uppercase or mixed case. ("Metadata entities" are thing like L, L, and L.) The default value is false. =item B Returns true if CLASS is a L used as part of a L relationship, false if it does not. The default implementations returns true if CLASS is derived from L and its L name looks like a map table name according to the L method and the L method returns either true or undef. Override this method to control which classes are considered map classes. Note that it may be called several times on the same class at various stages of that class's construction. =item B Given the class name CLASS, returns true if it looks like the name of a L used as part of a L relationship, false (but defined) if it does not, and undef if it's unsure. The default implementation returns true if CLASS is derived from L and has exactly two foreign keys. It returns false (but defined) if CLASS is derived from L and has been L (or if the foreign keys have been L) and the CLASS has no deferred foreign keys. It returns undef otherwise. =item B Returns true if TABLE looks like the name of a mapping table used as part of a L relationship, false (but defined) if it does not, and undef if it's unsure. The default implementation returns true if TABLE is in one of these forms: Regex Examples ----------------------- ----------------------------- (\w+_){2,}map pig_toe_map, pig_skin_toe_map (\w+_)*\w+_(\w+_)*\w+s pig_toes, pig_skin_toe_jams (\w+_)*\w+s_(\w+_)*\w+s pigs_toes, pig_skins_toe_jams It returns false otherwise. =item B Get or set the L object associated with the class that this convention manager belongs to. =item B Returns the singular version of STRING. If a L is defined, then this method simply passes STRING to that function. Otherwise, the following rules are applied, case-insensitively. * If STRING ends in "ies", then the "ies" is replaced with "y". * If STRING ends in "ses" then the "ses" is replaced with "s". * If STRING matches C, it is returned unmodified. For all other cases, the letter "s" is removed from the end of STRING and the result is returned. =item B Get or set a reference to the function used to convert strings to singular. The function should take a single string as an argument and return a singular version of the string. This function is undefined by default. =item B Given a table name and a local class name, return the name of the related class that fronts the table. To do this, L is called with TABLE and the L of LOCAL_CLASS passed as arguments. Examples: Table Local Class Related Class ----------- ------------ ---------------- prices My::Product My::Price big_hats A::B::FooBar A::B::BigHat a1_steaks Meat A1Steak =item B Returns the plural version of STRING. If a L is defined, then this method simply passes STRING to that function. Otherwise, the following rules are applied, case-insensitively, to form the plural. * If STRING ends in "x", "ss", or "es", then "es" is appended. * If STRING ends in "y" then the "y" is replaced with "ies". * If STRING ends in "s" then it is returned as-is. * Otherwise, "s" is appended. =item B Get or set a reference to the function used to convert strings to plural. The function should take a single string as an argument and return a plural version of the string. This function is undefined by default. =item B Let TABLE be the return value of the L method called on the L attribute of this object. If L is true, then TABLE is returned as-is. Otherwise, TABLE is passed to the L method and the result is returned. Otherwise, TABLE is returned as-is. =item B Let TABLE be the return value of the L method called on the L attribute of this object. If L is true, then TABLE is passed to the L method and the result is returned. Otherwise, TABLE is returned as-is. =item B Given a table name and an optional class prefix, return the corresponding class name. The prefix will be appended to the class name, if present. The prefix should end in "::". To do this, any letter that follows an underscore ("_") in the table name is replaced with an uppercase version of itself, and the underscore is removed. Examples: Table Prefix Class ----------- ------ ----------- products My:: My::Product products Product big_hats My:: My::BigHat my5_hat_pig My5HatPig =item B Get or set a boolean value that indicates whether or not table names are expected to be singular. The default value is false, meaning that table names are expected to be plural. =back =head1 PROTECTED API These methods are not part of the public interface, but are supported for use by subclasses. Put another way, given an unknown object that "isa" L, there should be no expectation that the following methods exist. But subclasses, which know the exact class from which they inherit, are free to use these methods in order to implement the public API described above. =over 4 =item B Override this method and return a reference to a function that takes a single string as an argument and returns a singular version of that string. =item B Override this method and return a reference to a function that takes a single string as an argument and returns a plural version of that string. =back =head1 TIPS AND TRICKS Much of the richness of a convention manager relies upon the quality of the L and L methods. The default implementations are primitive at best. For example, L will not correctly form the plural of the word "alumnus". One easy way to improve this is by setting a custom L. Here's an example using the handy L module: package My::Product; ... use Lingua::EN::Inflect; $cm = __PACKAGE__->meta->convention_manager; $cm->singular_to_plural_function(\&Lingua::EN::Inflect::PL); print $cm->singular_to_plural('person'); # "people" But that's a bit of a pain to do in every single class. An easier way to do it for all of your classes is to make a new L subclass that overrides the L method, then make a L-derived base class that uses your new metadata class. Example: package My::DB::Metadata; use Rose::DB::Object::Metadata; our @ISA = qw(Rose::DB::Object::Metadata); use Lingua::EN::Inflect; sub init_convention_manager { my $self = shift; # Let the base class make ths convention manager object my $cm = $self->SUPER::init_convention_manager(@_); # Set the new singular-to-plural function $cm->singular_to_plural_function(\&Lingua::EN::Inflect::PL); # Return the modified convention manager return $cm; } ... package My::DB::Object; use My::DB::Metadata; use Rose::DB::Object; our @ISA = qw(Rose::DB::Object); sub meta_class { 'My::DB::Metadata' } ... package My::Person; use My::DB::Object; our @ISA = qw(My::DB::Object); # The big pay-off: smart plurals! print __PACKAGE__->meta->table; # "people" You might wonder why I don't use L in L to save you this effort. The answer is that the L module adds almost a megabyte of memory overhead on my system. I'd rather not incur that overhead just for the sake of being more clever about naming conventions. Furthermore, as primitive as the default plural-forming is, at least it's deterministic. Guessing what L will return is not always easy, and the results can change depending on which version L you have installed. =head1 EXAMPLE Here's a complete example of nearly all of the major features of L. Let's start with the database schema. (This example uses PostgreSQL, but any L with native foreign key support will work.) CREATE TABLE vendors ( id SERIAL NOT NULL PRIMARY KEY, name VARCHAR(255) ); CREATE TABLE colors ( code CHAR(3) NOT NULL PRIMARY KEY, name VARCHAR(255) ); CREATE TABLE products ( id SERIAL NOT NULL PRIMARY KEY, name VARCHAR(255), vendor_id INT NOT NULL REFERENCES vendors (id) ); CREATE TABLE prices ( price_id SERIAL NOT NULL PRIMARY KEY, product_id INT NOT NULL REFERENCES products (id), region CHAR(2) NOT NULL DEFAULT 'US', price DECIMAL(10,2) NOT NULL ); CREATE TABLE product_colors ( id SERIAL NOT NULL PRIMARY KEY, product_id INT NOT NULL REFERENCES products (id), color_code CHAR(3) NOT NULL REFERENCES colors (code) ); Now the classes: # Rose::DB subclass to handle the db connection package My::DB; use base 'Rose::DB'; My::DB->register_db ( type => 'default', domain => 'default', driver => 'Pg', database => 'test', username => 'postgres', ); ... # Common Rose::DB::Object-derived base class for the other objects package My::Object; use My::DB; use base 'Rose::DB::Object'; sub init_db { My::DB->new } ... package My::Price; use base 'My::Object'; __PACKAGE__->meta->setup ( columns => [ price_id => { type => 'serial', not_null => 1 }, product_id => { type => 'int' }, region => { type => 'char', length => 2, default => 'US' }, price => { type => 'decimal', precision => 10, scale => 2 }, ], foreign_keys => [ 'product' ], ); ... package My::Vendor; use base 'My::Object'; __PACKAGE__->meta->setup ( columns => [ id => { type => 'serial', not_null => 1 }, name => { type => 'varchar', length => 255 }, ], ); ... package My::Color; use base 'My::Object'; __PACKAGE__->meta->setup ( columns => [ code => { type => 'char', length => 3, not_null => 1 }, name => { type => 'varchar', length => 255 }, ], ); ... package My::Product; use base 'My::Object'; __PACKAGE__->meta->setup ( columns => [ id => { type => 'serial', not_null => 1 }, name => { type => 'varchar', length => 255 }, vendor_id => { type => 'int' }, ], foreign_keys => [ 'vendor' ], relationships => [ prices => { type => 'one to many' }, colors => { type => 'many to many' }, ], ); ... package My::ProductColors; use base 'My::Object'; __PACKAGE__->meta->setup ( columns => [ qw(id product_id color_code) ], foreign_keys => [ 'product', 'color' ], ); Let's add some data: INSERT INTO vendors (id, name) VALUES (1, 'V1'); INSERT INTO vendors (id, name) VALUES (2, 'V2'); INSERT INTO products (id, name, vendor_id) VALUES (1, 'A', 1); INSERT INTO products (id, name, vendor_id) VALUES (2, 'B', 2); INSERT INTO products (id, name, vendor_id) VALUES (3, 'C', 1); INSERT INTO prices (product_id, region, price) VALUES (1, 'US', 1.23); INSERT INTO prices (product_id, region, price) VALUES (1, 'DE', 4.56); INSERT INTO prices (product_id, region, price) VALUES (2, 'US', 5.55); INSERT INTO prices (product_id, region, price) VALUES (3, 'US', 5.78); INSERT INTO prices (product_id, region, price) VALUES (3, 'US', 9.99); INSERT INTO colors (code, name) VALUES ('CC1', 'red'); INSERT INTO colors (code, name) VALUES ('CC2', 'green'); INSERT INTO colors (code, name) VALUES ('CC3', 'blue'); INSERT INTO colors (code, name) VALUES ('CC4', 'pink'); INSERT INTO product_colors (product_id, color_code) VALUES (1, 'CC1'); INSERT INTO product_colors (product_id, color_code) VALUES (1, 'CC2'); INSERT INTO product_colors (product_id, color_code) VALUES (2, 'CC4'); INSERT INTO product_colors (product_id, color_code) VALUES (3, 'CC2'); INSERT INTO product_colors (product_id, color_code) VALUES (3, 'CC3'); (Be aware that not all databases are smart enough to track explicitly setting serial column values as shown in the INSERT statements above. Subsequent auto-generated serial values may conflict with the explicitly set serial column values already in the table. Values are set explicitly here to make the examples easier to follow. In "real" code, you should let the serial columns populate automatically.) Finally, the classes in action: $p = My::Product->new(id => 1)->load; print $p->vendor->name, "\n"; # "V1" # "US: 1.23, DE: 4.56" print join(', ', map { $_->region .': '. $_->price } $p->prices), "\n"; # "red, green" print join(', ', map { $_->name } $p->colors), "\n"; =head1 AUTO-INIT EXAMPLE Using L's L feature, the Perl code can be reduced to an absurd degree. Given the same database schema and data shown in the L above, consider the following classes: package My::Auto::Color; use base 'My::Object'; __PACKAGE__->meta->auto_initialize; ... package My::Auto::Price; use base 'My::Object'; __PACKAGE__->meta->auto_initialize; ... package My::Auto::ProductColors; use base 'My::Object'; __PACKAGE__->meta->auto_initialize; ... package My::Auto::Vendor; use base 'My::Object'; __PACKAGE__->meta->auto_initialize; ... package My::Auto::Product; use base 'My::Object'; __PACKAGE__->meta->auto_initialize; Not a single table, column, foreign key, or relationship is specified, yet everything still works: $p = My::Auto::Product->new(id => 1)->load; print $p->vendor->name, "\n"; # "V1" # "US: 1.23, DE: 4.56" print join(', ', map { $_->region .': '. $_->price } $p->prices), "\n"; # "red, green" print join(', ', map { $_->name } $p->colors), "\n"; More precisely, everything still works I that you load all the of the related modules. For example, if you load C but don't load C (either from within the C class or in your program itself), then the C will not have a C method (since your program will have no knowledge of the C class). Use the L if you want to set up a bunch of related classes automatically without worrying about this kind of thing. Anyway, I don't recommend this kind of extreme approach, but it is an effective demonstration of the power of the convention manager. =head1 AUTHOR John C. Siracusa (siracusa@gmail.com) =head1 LICENSE Copyright (c) 2010 by John C. Siracusa. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Rose-DB-Object-0.820/lib/Rose/DB/Object/MixIn.pm000755 000765 000024 00000001354 12502134373 020764 0ustar00johnstaff000000 000000 package Rose::DB::Object::MixIn; use strict; use base 'Rose::Object::MixIn'; our $VERSION = '0.764'; 1; __END__ =head1 NAME Rose::DB::Object::MixIn - A base class for mix-ins. =head1 SYNOPSIS # This module is deprecated. Please use Rose::Object::MixIn instead. =head1 DESCRIPTION This class is a trivial subclass of L. It exists for backward compatibility reasons only. Please see L for full documentation, and use that class instead of this one. =head1 AUTHOR John C. Siracusa (siracusa@gmail.com) =head1 LICENSE Copyright (c) 2010 by John C. Siracusa. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Rose-DB-Object-0.820/lib/Rose/DB/Object/Metadata.pm000755 000765 000024 00000707036 14065625660 021504 0ustar00johnstaff000000 000000 package Rose::DB::Object::Metadata; use strict; use Carp(); use Rose::Object; our @ISA = qw(Rose::Object); use Rose::DB::Object::Util qw(lazy_column_values_loaded_key); use Rose::DB::Object::Constants qw(PRIVATE_PREFIX STATE_IN_DB MODIFIED_COLUMNS); use Rose::DB::Object::ConventionManager; use Rose::DB::Object::ConventionManager::Null; use Rose::DB::Object::Metadata::PrimaryKey; use Rose::DB::Object::Metadata::UniqueKey; use Rose::DB::Object::Metadata::ForeignKey; use Rose::DB::Object::Metadata::Column::Scalar; use Rose::DB::Object::Metadata::Relationship::OneToOne; # Attempt to load Scalar::Util::Clone at runtime and ignore any errors # to keep it from being a "hard" requirement. eval { local $@; require Scalar::Util::Clone }; use Clone(); # This is the backup clone method our $VERSION = '0.820'; our $Debug = 0; # # Object data # use Rose::Object::MakeMethods::Generic ( scalar => [ 'class', 'error', 'pre_init_hook', 'post_init_hook', '_object_default_manager_base_class', ], 'scalar --get_set_init' => [ 'db', 'db_id', 'primary_key', 'column_name_to_method_name_mapper', 'original_class', 'auto_prime_caches', ], boolean => [ allow_inline_column_values => { default => 0 }, is_initialized => { default => 0 }, is_auto_initializating => { default => 0 }, allow_auto_initialization => { default => 0 }, was_auto_initialized => { default => 0 }, initialized_foreign_keys => { default => 0 }, default_load_speculative => { default => 0 }, auto_load_related_classes => { default => 1 }, default_update_changes_only => { default => 0 }, default_insert_changes_only => { default => 0 }, default_cascade_save => { default => 0 }, default_smart_modification => { default => 0 }, include_predicated_unique_indexes => { default => 0 }, ], 'array --get_set_inited' => [ 'columns_ordered', 'nonpersistent_columns_ordered', ] ); # # Class data # use Rose::Class::MakeMethods::Generic ( inheritable_scalar => [ 'dbi_prepare_cached', 'default_column_undef_overrides_default', '_class_default_manager_base_class', ], inheritable_hash => [ column_type_classes => { interface => 'get_set_all' }, column_type_names => { interface => 'keys', hash_key => 'column_type_classes' }, _column_type_class => { interface => 'get_set', hash_key => 'column_type_classes' }, _delete_column_type_class => { interface => 'delete', hash_key => 'column_type_classes' }, auto_helper_classes => { interface => 'get_set_all' }, delete_auto_helper_class => { interface => 'delete', hash_key => 'auto_helper_classes' }, relationship_type_classes => { interface => 'get_set_all' }, relationship_type_class => { interface => 'get_set', hash_key => 'relationship_type_classes' }, delete_relationship_type_class => { interface => 'delete', hash_key => 'relationship_type_classes' }, class_registry => => { interface => 'get_set_all' }, convention_manager_classes => { interface => 'get_set_all' }, convention_manager_class => { interface => 'get_set', hash_key => 'convention_manager_classes' }, delete_convention_manager_class => { interface => 'delete', hash_key => 'convention_manager_classes' }, ], ); __PACKAGE__->default_manager_base_class('Rose::DB::Object::Manager'); __PACKAGE__->dbi_prepare_cached(1); __PACKAGE__->class_registry({}); __PACKAGE__->auto_helper_classes ( 'informix' => 'Rose::DB::Object::Metadata::Auto::Informix', 'pg' => 'Rose::DB::Object::Metadata::Auto::Pg', 'mysql' => 'Rose::DB::Object::Metadata::Auto::MySQL', 'sqlite' => 'Rose::DB::Object::Metadata::Auto::SQLite', 'oracle' => 'Rose::DB::Object::Metadata::Auto::Oracle', 'generic' => 'Rose::DB::Object::Metadata::Auto::Generic', ); __PACKAGE__->convention_manager_classes ( 'default' => 'Rose::DB::Object::ConventionManager', 'null' => 'Rose::DB::Object::ConventionManager::Null', ); __PACKAGE__->column_type_classes ( 'scalar' => 'Rose::DB::Object::Metadata::Column::Scalar', 'char' => 'Rose::DB::Object::Metadata::Column::Character', 'character' => 'Rose::DB::Object::Metadata::Column::Character', 'varchar' => 'Rose::DB::Object::Metadata::Column::Varchar', 'varchar2' => 'Rose::DB::Object::Metadata::Column::Varchar', 'nvarchar' => 'Rose::DB::Object::Metadata::Column::Varchar', 'nvarchar2' => 'Rose::DB::Object::Metadata::Column::Varchar', 'string' => 'Rose::DB::Object::Metadata::Column::Varchar', 'text' => 'Rose::DB::Object::Metadata::Column::Text', 'blob' => 'Rose::DB::Object::Metadata::Column::Blob', 'bytea' => 'Rose::DB::Object::Metadata::Column::Pg::Bytea', 'bits' => 'Rose::DB::Object::Metadata::Column::Bitfield', 'bitfield' => 'Rose::DB::Object::Metadata::Column::Bitfield', 'bool' => 'Rose::DB::Object::Metadata::Column::Boolean', 'boolean' => 'Rose::DB::Object::Metadata::Column::Boolean', 'int' => 'Rose::DB::Object::Metadata::Column::Integer', 'integer' => 'Rose::DB::Object::Metadata::Column::Integer', 'tinyint' => 'Rose::DB::Object::Metadata::Column::Integer', 'smallint' => 'Rose::DB::Object::Metadata::Column::Integer', 'mediumint' => 'Rose::DB::Object::Metadata::Column::Integer', 'bigint' => 'Rose::DB::Object::Metadata::Column::BigInt', 'serial' => 'Rose::DB::Object::Metadata::Column::Serial', 'bigserial' => 'Rose::DB::Object::Metadata::Column::BigSerial', 'enum' => 'Rose::DB::Object::Metadata::Column::Enum', 'num' => 'Rose::DB::Object::Metadata::Column::Numeric', #'number' => 'Rose::DB::Object::Metadata::Column::Numeric', 'numeric' => 'Rose::DB::Object::Metadata::Column::Numeric', 'decimal' => 'Rose::DB::Object::Metadata::Column::Numeric', 'float' => 'Rose::DB::Object::Metadata::Column::Float', 'float8' => 'Rose::DB::Object::Metadata::Column::DoublePrecision', 'double precision' => 'Rose::DB::Object::Metadata::Column::DoublePrecision', 'time' => 'Rose::DB::Object::Metadata::Column::Time', 'interval' => 'Rose::DB::Object::Metadata::Column::Interval', 'date' => 'Rose::DB::Object::Metadata::Column::Date', 'datetime' => 'Rose::DB::Object::Metadata::Column::Datetime', 'timestamp' => 'Rose::DB::Object::Metadata::Column::Timestamp', 'timestamp with time zone' => 'Rose::DB::Object::Metadata::Column::TimestampWithTimeZone', 'timestamp without time zone' => 'Rose::DB::Object::Metadata::Column::Timestamp', 'datetime year to fraction' => 'Rose::DB::Object::Metadata::Column::DatetimeYearToFraction', 'datetime year to fraction(1)' => 'Rose::DB::Object::Metadata::Column::DatetimeYearToFraction1', 'datetime year to fraction(2)' => 'Rose::DB::Object::Metadata::Column::DatetimeYearToFraction2', 'datetime year to fraction(3)' => 'Rose::DB::Object::Metadata::Column::DatetimeYearToFraction3', 'datetime year to fraction(4)' => 'Rose::DB::Object::Metadata::Column::DatetimeYearToFraction4', 'datetime year to fraction(5)' => 'Rose::DB::Object::Metadata::Column::DatetimeYearToFraction5', 'datetime year to second' => 'Rose::DB::Object::Metadata::Column::DatetimeYearToSecond', 'datetime year to minute' => 'Rose::DB::Object::Metadata::Column::DatetimeYearToMinute', 'datetime year to month' => 'Rose::DB::Object::Metadata::Column::DatetimeYearToMonth', 'epoch' => 'Rose::DB::Object::Metadata::Column::Epoch', 'epoch hires' => 'Rose::DB::Object::Metadata::Column::Epoch::HiRes', 'array' => 'Rose::DB::Object::Metadata::Column::Array', 'set' => 'Rose::DB::Object::Metadata::Column::Set', 'chkpass' => 'Rose::DB::Object::Metadata::Column::Pg::Chkpass', ); __PACKAGE__->relationship_type_classes ( 'one to one' => 'Rose::DB::Object::Metadata::Relationship::OneToOne', 'one to many' => 'Rose::DB::Object::Metadata::Relationship::OneToMany', 'many to one' => 'Rose::DB::Object::Metadata::Relationship::ManyToOne', 'many to many' => 'Rose::DB::Object::Metadata::Relationship::ManyToMany', ); # # Methods # sub init_column_name_to_method_name_mapper() { 0 } our %Objects; sub new { my($this_class, %args) = @_; my $class = $args{'class'} or Carp::croak "Missing required 'class' parameter"; return $Objects{$class} ||= shift->SUPER::new(@_); } sub init { my($self) = shift; # This attribute will be accessed many times, and a default # of 0 is usually a "faster false" than undef. $self->sql_qualify_column_names_on_load(0); $self->SUPER::init(@_); } sub init_original_class { ref shift } sub init_auto_prime_caches { $ENV{'MOD_PERL'} ? 1 : 0 } sub default_manager_base_class { my($self_or_class) = shift; if(ref($self_or_class)) { return $self_or_class->_object_default_manager_base_class(@_) || ref($self_or_class)->_class_default_manager_base_class; } return $self_or_class->_class_default_manager_base_class(@_); } sub reset { my($self) = shift; $self->is_initialized(0); $self->allow_auto_initialization(0); $self->was_auto_initialized(0); $self->initialized_foreign_keys(0); return; } sub clone { my($self) = shift; # The easy way: use Scalar::Util::Clone if(defined $Scalar::Util::Clone::VERSION) { return Scalar::Util::Clone::clone($self); } # The hard way: Clone.pm plus mucking my $meta = Clone::clone($self); # Reset all the parent back-links foreach my $item (grep { defined } $meta->columns, $meta->primary_key, $meta->unique_keys, $meta->foreign_keys, $meta->relationships) { $item->parent($meta); } return $meta; } sub allow_inheritance_from_meta { my($class, $meta) = @_; return $meta->num_columns > 0 ? 1 : 0; } sub for_class { my($meta_class, $class) = (shift, shift); return $Objects{$class} if($Objects{$class}); # Clone an ancestor meta object foreach my $parent_class (__get_parents($class)) { if(my $parent_meta = $Objects{$parent_class}) { next unless($meta_class->allow_inheritance_from_meta($parent_meta)); my $meta = $parent_meta->clone; $meta->reset(0); $meta->class($class); return $Objects{$class} = $meta; } } return $Objects{$class} = $meta_class->new(class => $class); } sub __get_parents { my($class) = shift; my @parents; no strict 'refs'; foreach my $sub_class (@{"${class}::ISA"}) { push(@parents, __get_parents($sub_class)) if($sub_class->isa('Rose::DB::Object')); } return $class, @parents; } sub clear_all_dbs { my($class) = shift; foreach my $obj_class ($class->registered_classes) { $obj_class->meta->db(undef); } } sub error_mode { return $_[0]->{'error_mode'} ||= $_[0]->init_error_mode unless(@_ > 1); my($self, $mode) = @_; unless($mode =~ /^(?:return|carp|croak|cluck|confess|fatal)$/) { Carp::croak "Invalid error mode: '$mode'"; } return $self->{'error_mode'} = $mode; } sub init_error_mode { 'fatal' } sub handle_error { my($self, $object) = @_; my $mode = $self->error_mode; return if($mode eq 'return'); my $level = $Carp::CarpLevel; local $Carp::CarpLevel = $level + 1; if($mode eq 'croak' || $mode eq 'fatal') { Carp::croak $object->error; } elsif($mode eq 'carp') { Carp::carp $object->error; } elsif($mode eq 'cluck') { Carp::cluck $object->error; } elsif($mode eq 'confess') { Carp::confess $object->error; } else { Carp::croak "(Invalid error mode set: '$mode') - ", $object->error; } return 1; } sub setup { my($self) = shift; return 1 if($self->is_initialized); my $init_args = []; my $auto_init = 0; PAIR: while(@_) { my $method = shift; if(ref $method eq 'CODE') { $method->($self); next PAIR; } my $args = shift; if($method =~ /^((?:auto_(?!helper)|(?:default_)?perl_)\w*)$/) { $self->init_auto_helper; } if($method eq 'initialize') { $init_args = ref $args ? $args : [ $args ]; next PAIR; } elsif($method eq 'auto_initialize' || $method eq 'auto') { unless($method eq 'auto' && !ref $args) { $init_args = ref $args ? $args : [ $args ]; } $auto_init = 1; next PAIR; } elsif($method eq 'helpers') { require Rose::DB::Object::Helpers; Rose::DB::Object::Helpers->import( '--target-class' => $self->class, (ref $args eq 'ARRAY' ? @$args : $args)); next PAIR; } unless($self->can($method)) { Carp::croak "Invalid parameter name: '$method'"; } if(ref $args eq 'ARRAY') { # Special case for the unique_key and add_unique_key methods # when the argument is a single array reference containing only # non-reference values if(($method eq 'unique_key' || $method eq 'add_unique_key') && !grep { ref } @$args) { $self->$method($args); } else { $self->$method(@$args); } } else { $self->$method($args); } } if($auto_init) { $self->auto_initialize(@$init_args); } else { $self->initialize(@$init_args); } return 1; } sub init_db { my($self) = shift; my $class = $self->class or die "Missing class!"; my $db = $self->class->init_db or Carp::croak "Could not init_db() for class $class - are you sure that ", "Rose::DB's data sources are set up?"; $self->{'db_id'} = $db->{'id'}; return $db; } sub init_db_id { my($self) = shift; $self->init_db; return $self->{'db_id'}; } sub init_convention_manager { shift->convention_manager_class('default')->new } sub convention_manager { my($self) = shift; if(@_) { my $mgr = shift; # Setting to undef means use the null convention manager if(!defined $mgr) { return $self->{'convention_manager'} = Rose::DB::Object::ConventionManager::Null->new(parent => $self); } elsif(!ref $mgr) { if(UNIVERSAL::isa($mgr, 'Rose::DB::Object::ConventionManager')) { $mgr = $mgr->new; } else { my $class = $self->convention_manager_class($mgr) or Carp::croak "No convention manager class registered under the name '$mgr'"; $mgr = $class->new; } } elsif(!UNIVERSAL::isa($mgr, 'Rose::DB::Object::ConventionManager')) { Carp::croak "$mgr is not a Rose::DB::Object::ConventionManager-derived object"; } $mgr->parent($self); return $self->{'convention_manager'} = $mgr; } if(defined $self->{'convention_manager'}) { return $self->{'convention_manager'}; } my $mgr = $self->init_convention_manager; $mgr->parent($self); return $self->{'convention_manager'} = $mgr; } sub cached_objects_expire_in { shift->class->cached_objects_expire_in(@_) } sub clear_object_cache { shift->class->clear_object_cache(@_) } sub prepare_select_options { @_ > 1 ? $_[0]->{'prepare_select_options'} = $_[1] : $_[0]->{'prepare_select_options'} ||= {} } sub prepare_insert_options { @_ > 1 ? $_[0]->{'prepare_insert_options'} = $_[1] : $_[0]->{'prepare_insert_options'} ||= {} } sub prepare_update_options { @_ > 1 ? $_[0]->{'prepare_update_options'} = $_[1] : $_[0]->{'prepare_update_options'} ||= {} } sub prepare_delete_options { @_ > 1 ? $_[0]->{'prepare_delete_options'} = $_[1] : $_[0]->{'prepare_delete_options'} ||= {} } sub prepare_bulk_delete_options { @_ > 1 ? $_[0]->{'prepare_bulk_delete_options'} = $_[1] : $_[0]->{'prepare_bulk_delete_options'} ||= $_[0]->prepare_delete_options; } sub prepare_bulk_update_options { @_ > 1 ? $_[0]->{'prepare_bulk_update_options'} = $_[1] : $_[0]->{'prepare_bulk_update_options'} ||= $_[0]->prepare_update_options; } sub prepare_options { my($self, $options) = @_; Carp::croak "Missing required hash ref argument to prepare_options()" unless(ref $options eq 'HASH'); $self->prepare_select_options({ %$options }); $self->prepare_insert_options({ %$options }); $self->prepare_update_options({ %$options }); $self->prepare_delete_options({ %$options }); } sub table { unless(@_ > 1) { return $_[0]->{'table'} ||= $_[0]->convention_manager->auto_table_name; } $_[0]->_clear_table_generated_values; return $_[0]->{'table'} = $_[1]; } sub catalog { return $_[0]->{'catalog'} unless(@_ > 1); $_[0]->_clear_table_generated_values; return $_[0]->{'catalog'} = $_[1]; } sub select_catalog { my($self, $db) = @_; return undef if($db && !$db->supports_catalog); return $self->{'catalog'} || ($db ? $db->catalog : undef); } sub schema { return $_[0]->{'schema'} unless(@_ > 1); $_[0]->_clear_table_generated_values; return $_[0]->{'schema'} = $_[1]; } sub select_schema { my($self, $db) = @_; return undef if($db && !$db->supports_schema); return $self->{'schema'} || ($db ? $db->schema : undef); } sub sql_qualify_column_names_on_load { my($self) = shift; if(@_) { my $value = $_[0] ? 1 : 0; no warnings 'uninitialized'; if($value != $self->{'sql_qualify_column_names_on_load'}) { $self->{'sql_qualify_column_names_on_load'} = $value; $self->_clear_column_generated_values; $self->prime_caches if($self->is_initialized); } } return $self->{'sql_qualify_column_names_on_load'}; } sub key_column_names { my($self) = shift; $self->{'key_column_names'} ||= [ $self->primary_key_columns, $self->unique_keys_column_names ]; return wantarray ? @{$self->{'key_column_names'}} : $self->{'key_column_names'}; } sub init_primary_key { Rose::DB::Object::Metadata::PrimaryKey->new(parent => shift); } sub primary_key_generator { shift->primary_key->generator(@_) } sub primary_key_columns { shift->primary_key->columns(@_) } sub primary_key_column_names { shift->primary_key->column_names(@_) } sub pk_columns { shift->primary_key_columns(@_) } sub primary_key_column_names_or_aliases { my($self) = shift; if($self->{'primary_key_column_names_or_aliases'}) { return $self->{'primary_key_column_names_or_aliases'}; } return $self->{'primary_key_column_names_or_aliases'} = [ map { $_->alias || $_->name } $self->primary_key_columns ]; } sub init_primary_key_column_info { my($self) = shift; my $pk_position = 0; foreach my $col_name ($self->primary_key_column_names) { $pk_position++; my $column = $self->column($col_name) or next; $column->is_primary_key_member(1); $column->primary_key_position($pk_position); } $self->_clear_primary_key_column_generated_values; # Init these by asking for them $self->primary_key_column_accessor_names; $self->primary_key_column_mutator_names; return; } sub add_primary_key_columns { my($self) = shift; $self->primary_key->add_columns(@_); $self->init_primary_key_column_info; return; } sub add_primary_key_column { shift->add_primary_key_columns(@_) } sub add_unique_keys { my($self) = shift; if(@_ == 1 && ref $_[0] eq 'ARRAY') { push @{$self->{'unique_keys'}}, Rose::DB::Object::Metadata::UniqueKey->new(parent => $self, columns => $_[0]); } else { push @{$self->{'unique_keys'}}, map { UNIVERSAL::isa($_, 'Rose::DB::Object::Metadata::UniqueKey') ? ($_->parent($self), $_) : ref $_ eq 'HASH' ? Rose::DB::Object::Metadata::UniqueKey->new(parent => $self, %$_) : Rose::DB::Object::Metadata::UniqueKey->new(parent => $self, columns => $_) } @_; } return; } sub unique_key_by_name { my($self, $name) = @_; foreach my $uk ($self->unique_keys) { return $uk if($uk->name eq $name); } return undef; } sub add_unique_key { shift->add_unique_keys(@_) } sub unique_key { \shift->add_unique_keys(@_) } sub delete_unique_keys { $_[0]->{'unique_keys'} = [] } sub unique_keys { my($self) = shift; if(@_) { $self->delete_unique_keys; $self->add_unique_keys(@_); } wantarray ? @{$self->{'unique_keys'} ||= []} : ($self->{'unique_keys'} ||= []); } sub unique_keys_column_names { wantarray ? map { scalar $_->column_names } @{shift->{'unique_keys'} ||= []} : [ map { scalar $_->column_names } @{shift->{'unique_keys'} ||= []} ]; } sub delete_column { my($self, $name) = @_; delete $self->{'columns'}{$name}; # Remove from ordered list too my $columns = $self->columns_ordered; for(my $i = 0; $i < @$columns; $i++) { if($columns->[$i]->name eq $name) { splice(@$columns, $i, 1); last; } } return; } sub delete_columns { my($self, $name) = @_; $self->{'columns'} = {}; $self->{'columns_ordered'} = []; return; } sub delete_nonpersistent_columns { my($self, $name) = @_; $self->{'nonpersistent_columns'} = {}; $self->{'nonpersistent_columns_ordered'} = []; return; } sub delete_nonpersistent_column { my($self, $name) = @_; delete $self->{'nonpersistent_columns'}{$name}; # Remove from ordered list too my $columns = $self->nonpersistent_columns_ordered; for(my $i = 0; $i < @$columns; $i++) { if($columns->[$i]->name eq $name) { splice(@$columns, $i, 1); last; } } return; } sub first_column { shift->columns_ordered->[0] } sub sync_keys_to_columns { my($self) = shift; $self->_clear_column_generated_values; my %columns = map { $_->name => 1 } $self->columns_ordered; foreach my $col_name ($self->primary_key_column_names) { unless($columns{$col_name}) { Carp::croak "Primary key column '$col_name' is not in the column list for ", $self->class; #$self->primary_key(undef); #last; } } my @valid_uks; UK: foreach my $uk ($self->unique_keys) { foreach my $col_name ($uk->column_names) { unless($columns{$col_name}) { Carp::croak "Column '$col_name' found in unique key is not in the column list for ", $self->class; #next UK; } } push(@valid_uks, $uk); } $self->unique_keys(@valid_uks); return; } sub replace_column { my($self) = shift; unless(@_ == 2) { Carp::croak "Missing column name and value arguments" if(@_ < 2); Carp::croak "Too many arguments passed to replace_column()" if(@_ < 2); } return $self->column(@_); } sub column { my($self, $name) = (shift, shift); if(@_) { $self->delete_column($name); $self->add_column($name => @_); } return $self->{'columns'}{$name} if($self->{'columns'}{$name}); return undef; } sub nonpersistent_column { my($self, $name) = (shift, shift); if(@_) { $self->delete_nonpersistent_column($name); $self->add_nonpersistent_column($name => @_); } return $self->{'nonpersistent_columns'}{$name} if($self->{'nonpersistent_columns'}{$name}); return undef; } sub columns { my($self) = shift; if(@_) { $self->delete_columns; $self->add_columns(@_); } return $self->columns_ordered; } sub nonpersistent_columns { my($self) = shift; if(@_) { $self->delete_nonpersistent_columns; $self->add_nonpersistent_columns(@_); } return $self->nonpersistent_columns_ordered; } sub num_columns { my($self) = shift; return $self->{'num_columns'} ||= scalar(@{$self->columns_ordered}); } sub nonlazy_columns { my($self) = shift; return wantarray ? (grep { !$_->lazy } $self->columns_ordered) : [ grep { !$_->lazy } $self->columns_ordered ]; } sub lazy_columns { my($self) = shift; return wantarray ? (grep { $_->lazy } $self->columns_ordered) : [ grep { $_->lazy } $self->columns_ordered ]; } # XXX: Super-lame code sharing via dynamically-scoped flag var our $Nonpersistent; sub add_nonpersistent_columns { local $Nonpersistent = 1; shift->_add_columns(@_); } sub add_nonpersistent_column { shift->add_nonpersistent_columns(@_) } sub add_columns { local $Nonpersistent = 0; shift->_add_columns(@_); } sub add_column { shift->add_columns(@_) } sub _add_columns { my($self) = shift; my $class = ref $self; my(@columns, @nonpersistent_columns); ARG: while(@_) { my $name = shift; if(UNIVERSAL::isa($name, 'Rose::DB::Object::Metadata::Column')) { my $column = $name; Carp::croak "Relationship $column lacks a name()" unless($column->name =~ /\S/); $column->parent($self); $column->nonpersistent(1) if($Nonpersistent); if($column->nonpersistent) { $self->{'nonpersistent_columns'}{$column->name} = $column; push(@nonpersistent_columns, $column); } else { $self->{'columns'}{$column->name} = $column; push(@columns, $column); } next; } unless(ref $_[0]) # bare column name, persistent only { my $column_class = $self->original_class->column_type_class('scalar') or Carp::croak "No column class set for column type 'scalar'"; #$Debug && warn $self->class, " - adding scalar column $name\n"; $self->{'columns'}{$name} = $column_class->new(name => $name, parent => $self); push(@columns, $self->{'columns'}{$name}); next; } if(UNIVERSAL::isa($_[0], 'Rose::DB::Object::Metadata::Column')) { my $column = $_[0]; $column->name($name); $column->parent($self); $column->nonpersistent(1) if($Nonpersistent); if($column->nonpersistent) { $self->{'nonpersistent_columns'}{$column->name} = $column; push(@nonpersistent_columns, $column); } else { $self->{'columns'}{$column->name} = $column; push(@columns, $column); } } elsif(ref $_[0] eq 'HASH') { my $info = shift; my $alias = $info->{'alias'}; if($info->{'primary_key'}) { #$Debug && warn $self->class, " - adding primary key column $name\n"; $self->add_primary_key_column($name); } my $methods = delete $info->{'methods'}; my $add_methods = delete $info->{'add_methods'}; if($methods && $add_methods) { Carp::croak "Cannot specify both 'methods' and 'add_methods' - ", "pick one or the other"; } my $type = $info->{'type'} ||= 'scalar'; my $column_class = $self->original_class->column_type_class($type) or Carp::croak "No column class set for column type '$type'"; unless($self->column_class_is_loaded($column_class)) { $self->load_column_class($column_class); } my %triggers; foreach my $event ($column_class->trigger_events) { $triggers{$event} = delete $info->{$event} if(exists $info->{$event}); } if(delete $info->{'temp'}) # coerce temp to nonpersistent { $info->{'nonpersistent'} = 1; } #$Debug && warn $self->class, " - adding $name $column_class\n"; # XXX: Order of args is important here! Parent must be set first # because some params rely on it being present when they're set. my $column = $column_class->new(parent => $self, %$info, name => $name); $column->nonpersistent(1) if($Nonpersistent); if($column->nonpersistent) { $self->{'nonpersistent_columns'}{$column->name} = $column; push(@nonpersistent_columns, $column); } else { $self->{'columns'}{$column->name} = $column; push(@columns, $column); } # Set or add auto-created method names if($methods || $add_methods) { my $auto_method_name = $methods ? 'auto_method_types' : 'add_auto_method_types'; my $methods_arg = $methods || $add_methods; if(ref $methods_arg eq 'HASH') { $methods = [ keys %$methods_arg ]; while(my($type, $name) = each(%$methods_arg)) { next unless(defined $name); $column->method_name($type => $name); } } else { $methods = $methods_arg; } $column->$auto_method_name($methods); } if(defined $alias) { $column->alias($alias); $self->alias_column($name, $alias); } if(%triggers) { while(my($event, $value) = each(%triggers)) { Carp::croak "Missing code reference for $event trigger" unless($value); foreach my $code (ref $value eq 'ARRAY' ? @$value : $value) { $column->add_trigger(event => $event, code => $code); } } } } else { Carp::croak "Invalid column name or specification: $_[0]"; } } # Handle as-yet undocumented smart modification defaults. # Smart modification is only relevant foreach my $column (@columns) { if($column->can('smart_modification') && !defined $column->{'smart_modification'}) { $column->smart_modification($self->default_smart_modification); } } if(@columns) { push(@{$self->{'columns_ordered'}}, @columns); $self->_clear_column_generated_values; } if(@nonpersistent_columns) { push(@{$self->{'nonpersistent_columns_ordered'}}, @nonpersistent_columns); $self->_clear_nonpersistent_column_generated_values; } return wantarray ? (@columns, @nonpersistent_columns) : [ @columns, @nonpersistent_columns ]; } sub relationship { my($self, $name) = (shift, shift); if(@_) { $self->delete_relationship($name); $self->add_relationship($name => $_[0]); } return $self->{'relationships'}{$name} if($self->{'relationships'}{$name}); return undef; } sub delete_relationship { my($self, $name) = @_; delete $self->{'relationships'}{$name}; return; } sub relationships { my($self) = shift; if(@_) { $self->delete_relationships; $self->add_relationships(@_); } return wantarray ? (sort { $a->name cmp $b->name } values %{$self->{'relationships'} ||= {}}) : [ sort { $a->name cmp $b->name } values %{$self->{'relationships'} ||= {}} ]; } sub delete_relationships { my($self) = shift; # Delete everything except fk proxy relationships foreach my $name (keys %{$self->{'relationships'} || {}}) { delete $self->{'relationships'}{$name} unless($self->{'relationships'}{$name}->foreign_key); } return; } sub add_relationships { my($self) = shift; my $class = ref $self; ARG: while(@_) { my $name = shift; # Relationship object if(UNIVERSAL::isa($name, 'Rose::DB::Object::Metadata::Relationship')) { my $relationship = $name; Carp::croak "Relationship $relationship lacks a name()" unless($relationship->name =~ /\S/); if(defined $self->{'relationships'}{$relationship->name}) { Carp::croak $self->class, " already has a relationship named '", $relationship->name, "'"; } $relationship->parent($self); $self->{'relationships'}{$relationship->name} = $relationship; next; } # Name and type only: recurse with hashref arg if(!ref $_[0]) { my $type = shift; $self->add_relationships($name => { type => $type }); next ARG; } if(UNIVERSAL::isa($_[0], 'Rose::DB::Object::Metadata::Relationship')) { my $relationship = shift; $relationship->name($name); $relationship->parent($self); $self->{'relationships'}{$name} = $relationship; } elsif(ref $_[0] eq 'HASH') { my $info = shift; if(defined $self->{'relationships'}{$name}) { Carp::croak $self->class, " already has a relationship named '$name'"; } my $methods = delete $info->{'methods'}; my $add_methods = delete $info->{'add_methods'}; if($methods && $add_methods) { Carp::croak "Cannot specify both 'methods' and 'add_methods' - ", "pick one or the other"; } my $type = $info->{'type'} or Carp::croak "Missing type parameter for relationship '$name'"; my $relationship = $self->{'relationships'}{$name} = $self->_build_relationship(name => $name, type => $type, info => $info); # Set or add auto-created method names if($methods || $add_methods) { my $auto_method_name = $methods ? 'auto_method_types' : 'add_auto_method_types'; my $methods_arg = $methods || $add_methods; if(ref $methods_arg eq 'HASH') { $methods = [ keys %$methods_arg ]; while(my($type, $name) = each(%$methods_arg)) { next unless(defined $name); $relationship->method_name($type => $name); } } else { $methods = $methods_arg; } $relationship->$auto_method_name($methods); } } else { Carp::croak "Invalid relationship name or specification: $_[0]"; } } } sub _build_relationship { my($self, %args) = @_; my $class = ref $self; my $name = $args{'name'} or Carp::croak "Missing name parameter"; my $info = $args{'info'} or Carp::croak "Missing info parameter"; my $type = $args{'type'} or Carp::croak "Missing type parameter for relationship '$name'"; my $relationship_class = $class->relationship_type_class($type) or Carp::croak "No relationship class set for relationship type '$type'"; unless($self->relationship_class_is_loaded($relationship_class)) { $self->load_relationship_class($relationship_class); } $Debug && warn $self->class, " - adding $name $relationship_class\n"; my $relationship = $self->convention_manager->auto_relationship($name, $relationship_class, $info) || $relationship_class->new(%$info, name => $name); unless($relationship) { Carp::croak "$class - Incomplete relationship specification could not be ", "completed by convention manager: $name"; } $relationship->parent($self); return $relationship; } sub add_relationship { shift->add_relationships(@_) } my %Class_Loaded; sub load_column_class { my($self, $column_class) = @_; unless(UNIVERSAL::isa($column_class, 'Rose::DB::Object::Metadata::Column')) { my $error; TRY: { local $@; eval "require $column_class"; $error = $@; } Carp::croak "Could not load column class '$column_class' - $error" if($error); } $Class_Loaded{$column_class}++; } sub column_class_is_loaded { $Class_Loaded{$_[1]} } sub column_type_class { my($class, $type) = (shift, shift); return $class->_column_type_class(lc $type, @_) } sub delete_column_type_class { my($class, $type) = (shift, shift); return $class->_delete_column_type_class(lc $type, @_) } sub load_relationship_class { my($self, $relationship_class) = @_; my $error; TRY: { local $@; eval "require $relationship_class"; $error = $@; } Carp::croak "Could not load relationship class '$relationship_class' - $error" if($error); $Class_Loaded{$relationship_class}++; } sub relationship_class_is_loaded { $Class_Loaded{$_[1]} } sub add_foreign_keys { my($self) = shift; ARG: while(@_) { my $name = shift; # Foreign key object if(UNIVERSAL::isa($name, 'Rose::DB::Object::Metadata::ForeignKey')) { my $fk = $name; Carp::croak "Foreign key $fk lacks a name()" unless($fk->name =~ /\S/); if(defined $self->{'foreign_keys'}{$fk->name}) { Carp::croak $self->class, " already has a foreign key named '", $fk->name, "'"; } $fk->parent($self); $self->{'foreign_keys'}{$fk->name} = $fk; unless(defined $self->relationship($fk->name)) { $self->add_relationship( $self->relationship_type_class($fk->relationship_type)->new( parent => $self, name => $fk->name, class => $fk->class, foreign_key => $fk)); } next ARG; } # Name only: try to get all the other info by convention if(!ref $_[0]) { if(my $fk = $self->convention_manager->auto_foreign_key($name)) { $self->add_foreign_keys($fk); next ARG; } else { Carp::croak $self->class, " - Incomplete foreign key specification could not be ", "completed by convention manager: $name"; } } # Name and hashref spec if(ref $_[0] eq 'HASH') { my $info = shift; if(defined $self->{'foreign_keys'}{$name}) { Carp::croak $self->class, " already has a foreign key named '$name'"; } my $methods = delete $info->{'methods'}; my $add_methods = delete $info->{'add_methods'}; if($methods && $add_methods) { Carp::croak "Cannot specify both 'methods' and 'add_methods' - ", "pick one or the other"; } $Debug && warn $self->class, " - adding $name foreign key\n"; my $fk = $self->{'foreign_keys'}{$name} = $self->convention_manager->auto_foreign_key($name, $info) || Rose::DB::Object::Metadata::ForeignKey->new(%$info, name => $name); $fk->parent($self); # Set or add auto-created method names if($methods || $add_methods) { my $auto_method_name = $methods ? 'auto_method_types' : 'add_auto_method_types'; my $methods_arg = $methods || $add_methods; if(ref $methods_arg eq 'HASH') { $methods = [ keys %$methods_arg ]; while(my($type, $name) = each(%$methods_arg)) { next unless(defined $name); $fk->method_name($type => $name); } } else { $methods = $methods_arg; } $fk->$auto_method_name($methods); } unless(defined $self->relationship($name)) { $self->add_relationship( $self->relationship_type_class($fk->relationship_type)->new( name => $name, class => $fk->class, foreign_key => $fk)); } } else { Carp::croak "Invalid foreign key specification: $_[0]"; } } } sub add_foreign_key { shift->add_foreign_keys(@_) } sub foreign_key { my($self, $name) = (shift, shift); if(@_) { $self->delete_foreign_key($name); $self->add_foreign_key($name => @_); } return $self->{'foreign_keys'}{$name} if($self->{'foreign_keys'}{$name}); return undef; } sub delete_foreign_key { my($self, $name) = @_; delete $self->{'foreign_keys'}{$name}; return; } sub delete_foreign_keys { my($self) = shift; # Delete fk proxy relationship foreach my $fk (values %{$self->{'foreign_keys'}}) { foreach my $rel ($self->relationships) { no warnings 'uninitialized'; if($rel->foreign_key eq $fk) { $self->delete_relationship($rel->name); } } } # Delete fks $self->{'foreign_keys'} = {}; return; } sub foreign_keys { my($self) = shift; if(@_) { $self->delete_foreign_keys; $self->add_foreign_keys(@_); } return wantarray ? (sort { $a->name cmp $b->name } values %{$self->{'foreign_keys'} ||= {}}) : [ sort { $a->name cmp $b->name } values %{$self->{'foreign_keys'} ||= {}} ]; } sub initialize { my($self) = shift; my(%args) = @_; $Debug && warn $self->class, " - initialize\n"; if(my $code = $self->pre_init_hook) { foreach my $sub (ref $code eq 'ARRAY' ? @$code : $code) { $sub->($self, @_); } } my $class = $self->class or Carp::croak "Missing class for metadata object $self"; $self->sync_keys_to_columns; my $table = $self->table; Carp::croak "$class - Missing table name" unless(defined $table && $table =~ /\S/); my @pk = $self->primary_key_column_names; Carp::croak "$class - Missing primary key for table '$table'" unless(@pk); $self->init_primary_key_column_info; my @column_names = $self->column_names; Carp::croak "$class - No columns defined for for table '$table'" unless(@column_names); foreach my $name ($self->primary_key_column_names) { my $column = $self->column($name) or Carp::croak "Could not find column for primary key column name '$name'"; if($column->is_lazy) { Carp::croak "Column '$name' cannot be lazy: cannot load primary key ", "columns on demand"; } } $self->make_methods(@_); $self->register_class; unless($args{'passive'}) { # Retry deferred stuff $self->retry_deferred_tasks; $self->retry_deferred_foreign_keys; $self->retry_deferred_relationships; } $self->refresh_lazy_column_tracking; unless($args{'stay_connected'}) { $self->db(undef); # make sure to ditch any db we may have retained } $self->is_initialized(1); $Debug && warn $self->class, " - initialized\n"; if(my $code = $self->post_init_hook) { foreach my $sub (ref $code eq 'ARRAY' ? @$code : $code) { $sub->($self, @_); } } # Regardless of cache priming, call this to ensure it's initialized, # since it is very likely to be used. $self->key_column_accessor_method_names_hash; $self->prime_caches if($self->auto_prime_caches); return; } use constant NULL_CATALOG => "\0"; use constant NULL_SCHEMA => "\0"; sub register_class { my($self) = shift; my $class = $self->class or Carp::croak "Missing class for metadata object $self"; my $db = $self->db; my $catalog = $self->select_catalog($db); my $schema = $db ? ($db->registration_schema || $self->select_schema($db)) : $self->select_schema($db);; $catalog = NULL_CATALOG unless(defined $catalog); $schema = NULL_SCHEMA unless(defined $schema); my $default_schema = $db ? $db->default_implicit_schema : undef; my $table = $self->table or Carp::croak "Missing table for metadata object $self"; $table = lc $table if($db->likes_lowercase_table_names); my $reg = $self->registry_key->class_registry; # Combine keys using $;, which is "\034" (0x1C) by default. But just to # make sure, I'll localize it. What I'm looking for is a value that # won't show up in a catalog, schema, or table name, so I'm guarding # against someone changing it to "-" (or whatever) elsewhere in the code. local $; = "\034"; # Register with all available information. # Ug, have to store lowercase versions too because MySQL sometimes returns # lowercase names for tables that are actually mixed case. Grrr... $reg->{'catalog-schema-table',$catalog,$schema,$table} = $reg->{'table',$table} = $reg->{'lc-catalog-schema-table',$catalog,$schema,lc $table} = $reg->{'lc-table',lc $table} = $class; $reg->{'catalog-schema-table',$catalog,$default_schema,$table} = $class if(defined $default_schema); push(@{$reg->{'classes'}}, $class); return; } sub registry_key { __PACKAGE__ } sub registered_classes { my($self) = shift; my $reg = $self->registry_key->class_registry; return wantarray ? @{$reg->{'classes'} ||= []} : $reg->{'classes'}; } sub unregister_all_classes { my($self) = shift; $self->registry_key->class_registry({}); return; } sub class_for { my($self_or_class, %args) = @_; my $self = ref($self_or_class) ? $self_or_class : undef; my $class = ref($self) || $self_or_class; my $db = $self ? $self->db : undef; my $catalog = $args{'catalog'}; my $schema = $args{'schema'}; $catalog = NULL_CATALOG unless(defined $catalog); $schema = NULL_SCHEMA unless(defined $schema); my $default_schema = $db ? $db->default_implicit_schema : undef; $default_schema = NULL_SCHEMA unless(defined $default_schema); my $table = $args{'table'} or Carp::croak "Missing required table parameter"; $table = lc $table if($db && $db->likes_lowercase_table_names); my $reg = $class->registry_key->class_registry; # Combine keys using $;, which is "\034" (0x1C) by default. But just to # make sure, we'll localize it. What we're looking for is a value that # wont' show up in a catalog, schema, or table name, so I'm guarding # against someone changing it to "-" elsewhere in the code or whatever. local $; = "\034"; my $f_class = $reg->{'catalog-schema-table',$catalog,$schema,$table} || $reg->{'catalog-schema-table',$catalog,$default_schema,$table} || ($schema eq NULL_SCHEMA && $default_schema eq NULL_SCHEMA ? $reg->{'lc-table',$table} : undef); # Ug, have to check lowercase versions too because MySQL sometimes returns # lowercase names for tables that are actually mixed case. Grrr... unless($f_class) { $table = lc $table; return $reg->{'lc-catalog-schema-table',$catalog,$schema,$table} || $reg->{'lc-catalog-schema-table',$catalog,$default_schema,$table} || ($schema eq NULL_SCHEMA && $default_schema eq NULL_SCHEMA ? $reg->{'lc-table',$table} : undef); } return $f_class; } #sub made_method_for_column #{ # (@_ > 2) ? ($_[0]->{'made_methods'}{$_[1]} = $_[2]) : # $_[0]->{'made_methods'}{$_[1]}; #} sub make_column_methods { my($self) = shift; my(%args) = @_; my $class = $self->class; $args{'target_class'} = $class; my $aliases = $self->column_aliases; while(my($column_name, $alias) = each(%$aliases)) { $self->column($column_name)->alias($alias); } foreach my $column ($self->columns_ordered) { unless($column->validate_specification) { Carp::croak "Column specification for column '", $column->name, "' in class ", $self->class, " is invalid: ", $column->error; } my $name = $column->name; my $method; foreach my $type ($column->auto_method_types) { $method = $self->method_name_from_column_name($name, $type) or Carp::croak "No method name defined for column '$name' ", "method type '$type'"; if(my $reason = $self->method_name_is_reserved($method, $class)) { Carp::croak "Cannot create method '$method' - $reason ", "Use alias_column() to map it to another name." } $column->method_name($type => $method); } #$Debug && warn $self->class, " - make methods for column $name\n"; $column->make_methods(%args); # XXX: Re-enabling the ability to alias primary keys #if($column->is_primary_key_member && $column->alias && $column->alias ne $column->name) #{ # Carp::croak "Primary key columns cannot be aliased (the culprit: '$name')"; #} # #if($method ne $name) #{ # # Primary key columns can be aliased, but we make a column-named # # method anyway. # foreach my $column ($self->primary_key_column_names) # { # if($name eq $column) # { # if(my $reason = $self->method_name_is_reserved($name, $class)) # { # Carp::croak # "Cannot create method for primary key column '$name' ", # "- $reason Although primary keys may be aliased, doing ", # "so will not avoid conflicts with reserved method names ", # "because a method named after the primary key column ", # "itself must also be created."; # } # # no strict 'refs'; # *{"${class}::$name"} = \&{"${class}::$method"}; # } # } #} } $self->_clear_column_generated_values; # Initialize method name hashes $self->column_accessor_method_names; $self->column_mutator_method_names; $self->column_rw_method_names; # This rule is relaxed for now... # Must have an rw accessor for every column #my $columns = $self->columns_ordered; # #unless(keys %methods == @$columns) #{ # Carp::croak "Rose::DB::Object-derived objects are required to have ", # "a 'get_set' method for every column. This class (", # $self->class, ") has ", scalar @$columns, "column", # (@$columns == 1 ? '' : 's'), " and ", scalar keys %methods, # " method", (scalar keys %methods == 1 ? '' : 's'); #} return; } sub make_nonpersistent_column_methods { my($self) = shift; my(%args) = @_; my $class = $self->class; $args{'target_class'} = $class; foreach my $column ($self->nonpersistent_columns_ordered) { unless($column->validate_specification) { Carp::croak "Column specification for column '", $column->name, "' in class ", $self->class, " is invalid: ", $column->error; } my $name = $column->name; my $method; foreach my $type ($column->auto_method_types) { $method = $self->method_name_from_column_name($name, $type) or Carp::croak "No method name defined for column '$name' ", "method type '$type'"; if(my $reason = $self->method_name_is_reserved($method, $class)) { Carp::croak "Cannot create method '$method' - $reason ", "Use alias_column() to map it to another name." } $column->method_name($type => $method); } #$Debug && warn $self->class, " - make methods for column $name\n"; $column->make_methods(%args); } $self->_clear_nonpersistent_column_generated_values; # Initialize method name hashes $self->nonpersistent_column_accessor_method_names; return; } sub make_foreign_key_methods { my($self) = shift; my(%args) = @_; #$self->retry_deferred_foreign_keys; my $class = $self->class; my $meta_class = ref $self; $args{'target_class'} = $class; foreach my $foreign_key ($self->foreign_keys) { #next unless($foreign_key->is_ready_to_make_methods); foreach my $type ($foreign_key->auto_method_types) { my $method = $foreign_key->method_name($type) || $foreign_key->build_method_name_for_type($type) || Carp::croak "No method name defined for foreign key '", $foreign_key->name, "' method type '$type'"; if(my $reason = $self->method_name_is_reserved($method, $class)) { Carp::croak "Cannot create method '$method' - $reason ", "Choose a different foreign key name." } $foreign_key->method_name($type => $method); } if($self->auto_load_related_classes && (my $fclass = $foreign_key->class)) { unless($fclass->isa('Rose::DB::Object')) { my $error; TRY: { local $@; eval "require $fclass"; $error = $@; } $Debug && print STDERR "FK REQUIRES $fclass - $error\n"; if($error) { # XXX: Need to distinguish recoverable errors from unrecoverable errors if($error !~ /\.pm in \@INC/ && !UNIVERSAL::isa($error, 'Rose::DB::Object::Exception::ClassNotReady')) { Carp::confess "Could not load $fclass - $error"; } } } } # We may need to defer the creation of some foreign key methods until # all the required pieces are loaded. if($foreign_key->is_ready_to_make_methods) { if($Debug && !$args{'preserve_existing'}) { warn $self->class, " - make methods for foreign key ", $foreign_key->name, "\n"; } $foreign_key->make_methods(%args); } else { # Confirm that no info is missing. This prevents an improperly # configured foreign_key from being deferred "forever" $foreign_key->sanity_check; $Debug && warn $self->class, " - defer foreign key ", $foreign_key->name, "\n"; $foreign_key->deferred_make_method_args(\%args); $meta_class->add_deferred_foreign_key($foreign_key); } # Keep foreign keys and their corresponding relationships in sync. my $fk_id = $foreign_key->id; my $fk_rel_type = $foreign_key->relationship_type; foreach my $relationship ($self->relationships) { next unless($relationship->type eq $fk_rel_type); if($fk_id eq $relationship->id) { $relationship->foreign_key($foreign_key); } } } $self->retry_deferred_foreign_keys; return; } our @Deferred_Tasks; sub deferred_tasks { return wantarray ? @Deferred_Tasks : \@Deferred_Tasks; } sub add_deferred_tasks { my($class) = shift; ARG: foreach my $arg (@_) { foreach my $task (@Deferred_Tasks) { next ARG if($arg->{'class'} eq $task->{'class'} && $arg->{'method'} eq $task->{'method'}); } push(@Deferred_Tasks, $arg); } } sub add_deferred_task { shift->add_deferred_tasks(@_) } sub has_deferred_tasks { my($self) = shift; my $class = $self->class; my $meta_class = ref $self; # Search among the deferred tasks too (icky) foreach my $task ($meta_class->deferred_tasks) { if($task->{'class'} eq $class) { return 1; } } return 0; } sub retry_deferred_tasks { my($self) = shift; my @tasks; foreach my $task (@Deferred_Tasks) { my $code = $task->{'code'}; my $check = $task->{'check'}; $code->(); unless($check->()) { push(@tasks, $task); } } if(join(',', sort @Deferred_Tasks) ne join(',', sort @tasks)) { @Deferred_Tasks = @tasks; } } our @Deferred_Foreign_Keys; sub deferred_foreign_keys { return wantarray ? @Deferred_Foreign_Keys : \@Deferred_Foreign_Keys; } sub has_deferred_foreign_keys { my($self) = shift; my $class = $self->class; my $meta_class = ref $self; foreach my $fk ($meta_class->deferred_foreign_keys) { return 1 if($fk->class eq $class); } # Search among the deferred tasks too (icky) foreach my $task ($meta_class->deferred_tasks) { if($task->{'class'} eq $class && $task->{'method'} eq 'auto_init_foreign_keys') { return 1; } } return 0; } sub has_outstanding_metadata_tasks { my($self) = shift; return $self->{'has_outstanding_metadata_tasks'} = shift if(@_); if(defined $self->{'has_outstanding_metadata_tasks'}) { return $self->{'has_outstanding_metadata_tasks'}; } if($self->has_deferred_foreign_keys || $self->has_deferred_relationships || $self->has_deferred_tasks) { return $self->{'has_outstanding_metadata_tasks'} = 1; } return $self->{'has_outstanding_metadata_tasks'} = 0; } sub add_deferred_foreign_keys { my($class) = shift; my $check = 0; ARG: foreach my $arg (@_) { foreach my $fk (@Deferred_Foreign_Keys) { next ARG if($fk->id eq $arg->id); } $arg->parent->has_outstanding_metadata_tasks(1); push(@Deferred_Foreign_Keys, $arg); } } sub add_deferred_foreign_key { shift->add_deferred_foreign_keys(@_) } sub retry_deferred_foreign_keys { my($self) = shift; my $meta_class = ref $self; my @foreign_keys; # Check to see if any deferred foreign keys are ready now foreach my $foreign_key ($meta_class->deferred_foreign_keys) { # XXX: this is not necessary, so it's commented out for now. # Try to rebuild the relationship using the convention manager, since # new info may be available now. Otherwise, leave it as-is. # $foreign_key = # $self->convention_manager->auto_foreign_key( # $def_fk->name, scalar $def_fk->spec_hash) || # $def_fk; if($foreign_key->is_ready_to_make_methods) { $Debug && warn $foreign_key->parent->class, " - (Retry) make methods for foreign key ", $foreign_key->name, "\n"; my $args = $foreign_key->deferred_make_method_args || {}; $foreign_key->make_methods(%$args); #, preserve_existing => 1); } else { push(@foreign_keys, $foreign_key); } } if(join(',', sort @Deferred_Foreign_Keys) ne join(',', sort @foreign_keys)) { @Deferred_Foreign_Keys = @foreign_keys; } # Retry relationship auto-init for all other classes foreach my $class ($self->registered_classes) { my $meta = $class->meta; next unless($meta->allow_auto_initialization && $meta->has_outstanding_metadata_tasks); $meta->auto_init_relationships(%{ $meta->auto_init_args || {} }, restore_types => 1); } } sub make_relationship_methods { my($self) = shift; my(%args) = @_; #$self->retry_deferred_relationships; my $meta_class = ref $self; my $class = $self->class; $args{'target_class'} = $class; my $preserve_existing_arg = $args{'preserve_existing'}; REL: foreach my $relationship ($self->relationships) { next if($args{'name'} && $relationship->name ne $args{'name'}); #next unless($relationship->is_ready_to_make_methods); foreach my $type ($relationship->auto_method_types) { my $method = $relationship->method_name($type) || $relationship->build_method_name_for_type($type) || Carp::croak "No method name defined for relationship '", $relationship->name, "' method type '$type'"; if(my $reason = $self->method_name_is_reserved($method, $class)) { Carp::croak "Cannot create method '$method' - $reason ", "Choose a different relationship name." } $relationship->method_name($type => $method); # Initialize/reset preserve_existing flag if($self->is_auto_initializating) { $args{'preserve_existing'} = $preserve_existing_arg || $self->allow_auto_initialization; } delete $args{'replace_existing'} if($args{'preserve_existing'}); # If a corresponding foreign key exists, the preserve any existing # methods with the same names. This is a crude way to ensure that we # can have a foreign key and a corresponding relationship without any # method name clashes. if($relationship->can('id')) { my $rel_id = $relationship->id; FK: foreach my $fk ($self->foreign_keys) { if($rel_id eq $fk->id) { $args{'preserve_existing'} = 1; delete $args{'replace_existing'}; last FK; } } } } if($self->auto_load_related_classes) { if($relationship->can('class')) { my $fclass = $relationship->class; unless($fclass->isa('Rose::DB::Object') && $fclass->meta->is_initialized) { my $error; TRY: { local $@; eval "require $fclass"; $error = $@; } $Debug && print STDERR "REL ", $relationship->name, " REQUIRES $fclass - $error\n"; if($error) { # XXX: Need to distinguish recoverable errors from unrecoverable errors if($error !~ /\.pm in \@INC/ && !UNIVERSAL::isa($error, 'Rose::DB::Object::Exception::ClassNotReady')) #if($error =~ /syntax error at |requires explicit package name|not allowed while "strict|already has a relationship named|Can't modify constant item/) { Carp::confess "Could not load $fclass - $error"; } } } } if($relationship->can('map_class')) { my $map_class = $relationship->map_class; unless($map_class->isa('Rose::DB::Object') && $map_class->meta->is_initialized) { my $error; TRY: { local $@; eval "require $map_class"; $error = $@; } $Debug && print STDERR "REL ", $relationship->name, " REQUIRES $map_class - $error\n"; if($error) { # XXX: Need to distinguish recoverable errors from unrecoverable errors if($error !~ /\.pm in \@INC/ && !UNIVERSAL::isa($error, 'Rose::DB::Object::Exception::ClassNotReady')) #if($error =~ /syntax error at |requires explicit package name|not allowed while "strict|already has a relationship named|Can't modify constant item/) { Carp::confess "Could not load $map_class - $error"; } } } } } # We may need to defer the creation of some relationship methods until # all the required pieces are loaded. if($relationship->is_ready_to_make_methods) { if($Debug && !$args{'preserve_existing'}) { warn $self->class, " - make methods for relationship ", $relationship->name, "\n"; } $relationship->make_methods(%args); } elsif(!$relationship->can('foreign_key') || !$relationship->foreign_key) { # Confirm that no info is missing. This prevents an improperly # configured relationship from being deferred "forever" $relationship->sanity_check; $Debug && warn $self->class, " - defer relationship ", $relationship->name, "\n"; $relationship->deferred_make_method_args(\%args); $meta_class->add_deferred_relationship($relationship); } } #$self->retry_deferred_relationships; return; } our @Deferred_Relationships; sub deferred_relationships { return wantarray ? @Deferred_Relationships : \@Deferred_Relationships; } sub has_deferred_relationships { my($self) = shift; my $class = $self->class; my $meta_class = ref $self; foreach my $rel ($meta_class->deferred_relationships) { if(($rel->can('class') && $rel->class eq $class) || ($rel->can('map_class') && $rel->map_class eq $class)) { return 1; } } # Search among the deferred tasks too (icky) foreach my $task ($meta_class->deferred_tasks) { if($task->{'class'} eq $class && $task->{'method'} eq 'auto_init_relationships') { return 1; } } return 0; } sub add_deferred_relationships { my($class) = shift; ARG: foreach my $arg (@_) { foreach my $rel (@Deferred_Relationships) { next ARG if($rel->id eq $arg->id); } push(@Deferred_Relationships, $arg); } } sub add_deferred_relationship { shift->add_deferred_relationships(@_) } sub retry_deferred_relationships { my($self) = shift; my $meta_class = ref $self; my @relationships; # Check to see if any deferred relationships are ready now foreach my $relationship ($self->deferred_relationships) { # Try to rebuild the relationship using the convention manager, since # new info may be available now. Otherwise, leave it as-is. my $rebuild_rel = $self->convention_manager->auto_relationship( $relationship->name, ref $relationship, scalar $relationship->spec_hash); if($rebuild_rel) { # XXX: This is pretty evil. I need some sort of copy operator, but # XXX: a straight hash copy will do for now... %$relationship = %$rebuild_rel; } if($relationship->is_ready_to_make_methods) { $Debug && warn $relationship->parent->class, " - (Retry) make methods for relationship ", $relationship->name, "\n"; my $args = $relationship->deferred_make_method_args || {}; $args->{'preserve_existing'} = 1; delete $args->{'replace_existing'}; $relationship->make_methods(%$args); # Reassign to list in case we rebuild above $relationship->parent->relationship($relationship->name => $relationship); } else { push(@relationships, $relationship); } } if(join(',', sort @Deferred_Relationships) ne join(',', sort @relationships)) { @Deferred_Relationships = @relationships; } # Retry relationship auto-init for all other classes #foreach my $class ($self->registered_classes) #{ # next unless($class->meta->allow_auto_initialization && $meta->has_outstanding_metadata_tasks); # $self->auto_init_relationships(restore_types => 1); #} } sub make_methods { my($self) = shift; $self->make_column_methods(@_); $self->make_nonpersistent_column_methods(@_); $self->make_foreign_key_methods(@_); $self->make_relationship_methods(@_); } sub generate_primary_key_values { my($self, $db) = @_; if(my $code = $self->primary_key_generator) { return $code->($self, $db); } my @ids; my $seqs = $self->fq_primary_key_sequence_names(db => $db); if($seqs && @$seqs) { my $i = 0; foreach my $seq (@$seqs) { $i++; unless(defined $seq) { push(@ids, undef); next; } my $id = $db->next_value_in_sequence($seq); unless($id) { $self->error("Could not generate primary key for ", $self->class, " column '", ($self->primary_key_column_names)[$i], "' by selecting the next value in the sequence ", "'$seq' - $@"); return undef; } push(@ids, $id); } return @ids; } else { return $db->generate_primary_key_values(scalar @{$self->primary_key_column_names}); } } sub generate_primary_key_value { my @ids = shift->generate_primary_key_values(@_); return $ids[0]; } sub generate_primary_key_placeholders { my($self, $db) = @_; return $db->generate_primary_key_placeholders(scalar @{$self->primary_key_column_names}); } sub primary_key_column_accessor_names { my($self) = shift; if($self->{'primary_key_column_accessor_names'}) { return @{$self->{'primary_key_column_accessor_names'}}; } my @column_names = $self->primary_key_column_names; my @columns = grep { defined } map { $self->column($_) } @column_names; return unless(@column_names == @columns); # not ready yet my @methods = grep { defined } map { $self->column_accessor_method_name($_) } @column_names; return unless(@methods); $self->{'primary_key_column_accessor_names'} = \@methods; return @methods; } sub primary_key_column_mutator_names { my($self) = shift; if($self->{'primary_key_column_mutator_names'}) { return @{$self->{'primary_key_column_mutator_names'}}; } my @column_names = $self->primary_key_column_names; my @columns = grep { defined } map { $self->column($_) } @column_names; return unless(@column_names == @columns); # not ready yet my @methods = grep { defined } map { $self->column_mutator_method_name($_) } @column_names; return unless(@methods); $self->{'primary_key_column_mutator_names'} = \@methods; return @methods; } sub fq_primary_key_sequence_names { my($self, %args) = @_; my $db_id = $args{'db'}{'id'} || ($self->{'db_id'} ||= $self->init_db_id); if(defined $self->{'fq_primary_key_sequence_names'}{$db_id}) { my $seqs = $self->{'fq_primary_key_sequence_names'}{$db_id} or return; return wantarray ? @$seqs : $seqs; } my $db = $args{'db'} or die "Cannot generate fully-qualified primary key sequence name without db argument"; my @seqs = $self->primary_key_sequence_names($db); if(@seqs) { $self->primary_key->sequence_names(@seqs); # Add schema and catalog information only if it isn't present # XXX: crappy check - just looking for a '.' foreach my $seq (@seqs) { if(defined $seq && index($seq, '.') < 0) { $seq = $db->quote_identifier_for_sequence($self->select_catalog($db), $self->select_schema($db), $seq); } } $self->{'fq_primary_key_sequence_names'}{$db->{'id'}} = \@seqs; return wantarray ? @seqs : \@seqs; } return; } sub refresh_primary_key_sequence_names { my($self, $db) = @_; my $db_id = UNIVERSAL::isa($db, 'Rose::DB') ? $db->id : $db; $self->{'fq_primary_key_sequence_names'}{$db_id} = undef; $self->{'primary_key_sequence_names'}{$db_id} = undef; return; } sub primary_key_sequence_names { my($self) = shift; my($db, $db_id); $db = shift if(UNIVERSAL::isa($_[0], 'Rose::DB')); $db_id = $db ? $db->{'id'} : $self->init_db_id; # Set pk sequence names if(@_) { # Clear fully-qualified pk values $self->{'fq_primary_key_sequence_names'}{$db_id} = undef; my $ret = $self->{'primary_key_sequence_names'}{$db_id} = (@_ == 1 && ref $_[0]) ? $_[0] : [ @_ ]; # Push down into pk metadata object too $self->primary_key->sequence_names(($db ? $db : ()), @$ret); return wantarray ? @$ret : $ret; } if($self->{'primary_key_sequence_names'}{$db_id}) { my $ret = $self->{'primary_key_sequence_names'}{$db_id}; return wantarray ? @$ret : $ret; } # Init pk sequence names # Start by considering the list of sequence names stored in the # primary key metadata object my @pks = $self->primary_key_column_names; my $seqs = $self->primary_key->sequence_names($db); my @seqs; if($seqs) { # If each pk column has a defined sequence name, accept them as-is if(@pks == grep { defined } @$seqs) { $self->{'primary_key_sequence_names'}{$db_id} = $seqs; return wantarray ? @$seqs : $seqs; } else # otherwise, use them as a starting point { @seqs = @$seqs; } } unless($db) { die "Cannot generate primary key sequence name without db argument"; } my $cm = $self->convention_manager; my $table = $self->table or Carp::croak "Cannot generate primary key sequence name without table name"; my $i = 0; foreach my $column ($self->primary_key_columns) { my $seq; # Go the extra mile and look up the sequence name (if any) for scalar # pk columns. These pk columns were probably set using the columns() # shortcut $meta->columns(qw(foo bar baz)) rather than the "long way" # with type information. if($column->type eq 'scalar') { $seq = $self->_sequence_name($db, $self->select_catalog($db), $self->select_schema($db), $table, $column); } # Set auto-created serial column sequence names elsif($column->type =~ /^(?:big)?serial$/ && $db->use_auto_sequence_name) { $seq = $cm->auto_column_sequence_name($table, $column, $db); } unless(exists $seqs[$i] && defined $seqs[$i]) { $seqs[$i] = $seq if(defined $seq); } $i++; } # Only save if it looks like the class setup is finished if($self->is_initialized) { $self->{'primary_key_sequence_names'}{$db->{'id'}} = \@seqs; } return wantarray ? @seqs : \@seqs; } sub _sequence_name { my($self, $db, $catalog, $schema, $table, $column) = @_; # XXX: This is only beneficial in PostgreSQL right now return unless($db->driver eq 'pg'); $table = lc $table if($db->likes_lowercase_table_names); my($col_info, $error); TRY: { local $@; eval { my $dbh = $db->dbh; local $dbh->{'RaiseError'} = 0; local $dbh->{'PrintError'} = 0; my $sth = $dbh->column_info($catalog, $schema, $table, $column) or return; $sth->execute; $col_info = $sth->fetchrow_hashref; $sth->finish; }; $error = $@; } return if($error || !$col_info); $db->refine_dbi_column_info($col_info, $self); my $seq = $col_info->{'rdbo_default_value_sequence_name'}; my $implicit_schema = $db->default_implicit_schema; # Strip off default implicit schema unless a schema is explicitly specified if(defined $seq && defined $implicit_schema && !defined $schema) { $seq =~ s/^$implicit_schema\.//; } return $seq; } sub column_names { my($self) = shift; $self->{'column_names'} ||= [ map { $_->name } $self->columns_ordered ]; return wantarray ? @{$self->{'column_names'}} : $self->{'column_names'}; } sub nonpersistent_column_names { my($self) = shift; $self->{'nonpersistent_column_names'} ||= [ map { $_->name } $self->nonpersistent_columns_ordered ]; return wantarray ? @{$self->{'nonpersistent_column_names'}} : $self->{'nonpersistent_column_names'}; } sub nonlazy_column_names { my($self) = shift; $self->{'nonlazy_column_names'} ||= [ map { $_->name } $self->nonlazy_columns ]; return wantarray ? @{$self->{'nonlazy_column_names'}} : $self->{'nonlazy_column_names'}; } sub lazy_column_names { my($self) = shift; $self->{'lazy_column_names'} ||= [ map { $_->name } $self->lazy_columns ]; return wantarray ? @{$self->{'lazy_column_names'}} : $self->{'lazy_column_names'}; } sub nonlazy_column_names_string_sql { my($self, $db) = @_; return $self->{'nonlazy_column_names_string_sql'}{$db->{'id'}} ||= join(', ', map { $_->name_sql($db) } $self->nonlazy_columns); } sub column_names_string_sql { my($self, $db) = @_; return $self->{'column_names_string_sql'}{$db->{'id'}} ||= join(', ', map { $_->name_sql($db) } $self->columns_ordered); } sub column_names_sql { my($self, $db) = @_; my $list = $self->{'column_names_sql'}{$db->{'id'}} ||= [ map { $_->name_sql($db) } $self->columns_ordered ]; return wantarray ? @$list : $list; } sub select_nonlazy_columns_string_sql { my($self, $db) = @_; return $self->{'select_nonlazy_columns_string_sql'}{$db->{'id'}} ||= join(', ', @{ scalar $self->select_nonlazy_columns_sql($db) }); } sub select_columns_string_sql { my($self, $db) = @_; return $self->{'select_columns_string_sql'}{$db->{'id'}} ||= join(', ', @{ scalar $self->select_columns_sql($db) }); } sub select_columns_sql { my($self, $db) = @_; my $list = $self->{'select_columns_sql'}{$db->{'id'}}; unless($list) { my $table = $self->table; if($self->sql_qualify_column_names_on_load) { $list = [ map { $_->select_sql($db, $table) } $self->columns_ordered ]; } else { $list = [ map { $_->select_sql($db) } $self->columns_ordered ]; } $self->{'select_columns_sql'}{$db->{'id'}} = $list; } return wantarray ? @$list : $list; } sub select_nonlazy_columns_sql { my($self, $db) = @_; my $list = $self->{'select_nonlazy_columns_sql'}{$db->{'id'}}; unless($list) { my $table = $self->table; if($self->sql_qualify_column_names_on_load) { $list = [ map { $_->select_sql($db, $table) } $self->nonlazy_columns ]; } else { $list = [ map { $_->select_sql($db) } $self->nonlazy_columns ]; } $self->{'select_nonlazy_columns_sql'}{$db->{'id'}} = $list; } return wantarray ? @$list : $list; } sub method_column { my($self, $method) = @_; unless(defined $self->{'method_columns'}) { foreach my $column ($self->columns_ordered) { foreach my $type ($column->defined_method_types) { if(my $method = $column->method_name($type)) { $self->{'method_column'}{$method} = $column; } } } } return $self->{'method_column'}{$method}; } sub column_rw_method_names { my($self) = shift; $self->{'column_rw_method_names'} ||= [ map { $self->column_rw_method_name($_) } $self->column_names ]; return wantarray ? @{$self->{'column_rw_method_names'}} : $self->{'column_rw_method_names'}; } sub column_accessor_method_names { my($self) = shift; $self->{'column_accessor_method_names'} ||= [ map { $self->column_accessor_method_name($_) } $self->column_names ]; return wantarray ? @{$self->{'column_accessor_method_names'}} : $self->{'column_accessor_method_names'}; } sub nonpersistent_column_accessor_method_names { my($self) = shift; $self->{'nonpersistent_column_accessor_method_names'} ||= [ map { $self->nonpersistent_column_accessor_method_name($_) } $self->nonpersistent_column_names ]; return wantarray ? @{$self->{'nonpersistent_column_accessor_method_names'}} : $self->{'nonpersistent_column_accessor_method_names'}; } sub nonlazy_column_accessor_method_names { my($self) = shift; $self->{'nonlazy_column_accessor_method_names'} ||= [ map { $self->column_accessor_method_name($_) } $self->nonlazy_column_names ]; return wantarray ? @{$self->{'nonlazy_column_accessor_method_names'}} : $self->{'nonlazy_column_accessor_method_names'}; } sub column_mutator_method_names { my($self) = shift; $self->{'column_mutator_method_names'} ||= [ map { $self->column_mutator_method_name($_) } $self->column_names ]; return wantarray ? @{$self->{'column_mutator_method_names'}} : $self->{'column_mutator_method_names'}; } sub nonpersistent_column_mutator_method_names { my($self) = shift; $self->{'nonpersistent_column_mutator_method_names'} ||= [ map { $self->nonpersistent_column_mutator_method_name($_) } $self->nonpersistent_column_names ]; return wantarray ? @{$self->{'nonpersistent_column_mutator_method_names'}} : $self->{'nonpersistent_column_mutator_method_names'}; } sub nonlazy_column_mutator_method_names { my($self) = shift; $self->{'nonlazy_column_mutator_method_names'} ||= [ map { $self->column_mutator_method_name($_) } $self->nonlazy_column_names ]; return wantarray ? @{$self->{'nonlazy_column_mutator_method_names'}} : $self->{'nonlazy_column_mutator_method_names'}; } sub column_db_value_hash_keys { my($self) = shift; $self->{'column_db_value_hash_keys'} ||= { map { $_->mutator_method_name => $_->db_value_hash_key } $self->columns_ordered }; return wantarray ? %{$self->{'column_db_value_hash_keys'}} : $self->{'column_db_value_hash_keys'}; } sub nonlazy_column_db_value_hash_keys { my($self) = shift; $self->{'nonlazy_column_db_value_hash_keys'} ||= { map { $_->mutator_method_name => $_->db_value_hash_key } $self->nonlazy_columns }; return wantarray ? %{$self->{'nonlazy_column_db_value_hash_keys'}} : $self->{'nonlazy_column_db_value_hash_keys'}; } sub primary_key_column_db_value_hash_keys { my($self) = shift; $self->{'primary_key_column_db_value_hash_keys'} ||= [ map { $_->db_value_hash_key } $self->primary_key_columns ]; return wantarray ? @{$self->{'primary_key_column_db_value_hash_keys'}} : $self->{'primary_key_column_db_value_hash_keys'}; } sub alias_column { my($self, $name, $new_name) = @_; Carp::croak "Usage: alias_column(column name, new name)" unless(@_ == 3); Carp::croak "No such column '$name' in table ", $self->table unless($self->{'columns'}{$name}); Carp::cluck "Pointless alias for '$name' to '$new_name' for table ", $self->table unless($name ne $new_name); # XXX: Allow primary keys to be aliased # XXX: Was disabled because the Manager was not happy with this. #foreach my $column ($self->primary_key_column_names) #{ # if($name eq $column) # { # Carp::croak "Primary key columns cannot be aliased (the culprit: '$name')"; # } #} $self->_clear_column_generated_values; if(my $column = $self->column($name)) { $column->method_name($new_name); } $self->{'column_aliases'}{$name} = $new_name; } sub column_aliases { return $_[0]->{'column_aliases'} unless(@_ > 1); return $_[0]->{'column_aliases'} = (ref $_[1] eq 'HASH') ? $_[1] : { @_[1 .. $#_] }; } sub column_accessor_method_name { $_[0]->{'column_accessor_method'}{$_[1]} ||= ($_[0]->column($_[1]) ? $_[0]->column($_[1])->accessor_method_name : undef); } sub nonpersistent_column_accessor_method_name { $_[0]->{'nonpersistent_column_accessor_method'}{$_[1]} ||= ($_[0]->nonpersistent_column($_[1]) ? $_[0]->nonpersistent_column($_[1])->accessor_method_name : undef); } sub column_accessor_method_names_hash { shift->{'column_accessor_method'} } sub nonpersistent_column_accessor_method_names_hash { shift->{'nonpersistent_column_accessor_method'} } sub key_column_accessor_method_names_hash { my($self) = shift; return $self->{'key_column_accessor_method'} if($self->{'key_column_accessor_method'}); foreach my $column (grep { ref } $self->primary_key_columns) { $self->{'key_column_accessor_method'}{$column->name} = $column->accessor_method_name; } foreach my $uk ($self->unique_keys) { foreach my $column (grep { ref } $uk->columns) { $self->{'key_column_accessor_method'}{$column->name} = $column->accessor_method_name; } } return $self->{'key_column_accessor_method'}; } sub column_mutator_method_name { $_[0]->{'column_mutator_method'}{$_[1]} ||= ($_[0]->column($_[1]) ? $_[0]->column($_[1])->mutator_method_name : undef); } sub nonpersistent_column_mutator_method_name { $_[0]->{'nonpersistent_column_mutator_method'}{$_[1]} ||= ($_[0]->nonpersistent_column($_[1]) ? $_[0]->nonpersistent_column($_[1])->mutator_method_name : undef); } sub column_mutator_method_names_hash { shift->{'column_mutator_method'} } sub column_rw_method_name { $_[0]->{'column_rw_method'}{$_[1]} ||= $_[0]->column($_[1])->rw_method_name; } sub column_rw_method_names_hash { shift->{'column_rw_method'} } sub fq_table_sql { my($self, $db) = @_; return $self->{'fq_table_sql'}{$db->{'id'}} ||= join('.', grep { defined } ($self->select_catalog($db), $self->select_schema($db), $db->auto_quote_table_name($self->table))); } sub fqq_table_sql { my($self, $db) = @_; return $self->{'fq_table_sql'}{$db->{'id'}} ||= join('.', grep { defined } ($self->select_catalog($db), $self->select_schema($db), $db->quote_table_name($self->table))); } sub fq_table { my($self, $db) = @_; return $self->{'fq_table'}{$db->{'id'}} ||= join('.', grep { defined } ($self->select_catalog($db), $self->select_schema($db), $self->table)); } sub load_all_sql { my($self, $key_columns, $db) = @_; $key_columns ||= $self->primary_key_column_names; no warnings; return $self->{'load_all_sql'}{$db->{'id'}}{join("\0", @$key_columns)} ||= 'SELECT ' . $self->select_columns_string_sql($db) . ' FROM ' . $self->fq_table_sql($db) . ' WHERE ' . join(' AND ', map { my $c = $self->column($_); ($self->sql_qualify_column_names_on_load ? $db->auto_quote_column_with_table($c->name_sql, $self->table) : $c->name_sql($db)) . ' = ' . $c->query_placeholder_sql($db) } @$key_columns); } sub load_sql { my($self, $key_columns, $db) = @_; $key_columns ||= $self->primary_key_column_names; no warnings; return $self->{'load_sql'}{$db->{'id'}}{join("\0", @$key_columns)} ||= 'SELECT ' . $self->select_nonlazy_columns_string_sql($db) . ' FROM ' . $self->fq_table_sql($db) . ' WHERE ' . join(' AND ', map { my $c = $self->column($_); ($self->sql_qualify_column_names_on_load ? $db->auto_quote_column_with_table($c->name_sql, $self->table) : $c->name_sql($db)) . ' = ' . $c->query_placeholder_sql($db) } @$key_columns); } sub load_all_sql_with_null_key { my($self, $key_columns, $key_values, $db) = @_; my $i = 0; my $fq = $self->sql_qualify_column_names_on_load; my $table = $self->table; no warnings; return 'SELECT ' . $self->select_columns_string_sql($db) . ' FROM ' . $self->fq_table_sql($db) . ' WHERE ' . join(' AND ', map { my $c = $self->column($_); ($fq ? $db->auto_quote_column_with_table($c->name_sql, $table) : $c->name_sql($db)) . (defined $key_values->[$i++] ? ' = ' . $c->query_placeholder_sql : ' IS NULL') } @$key_columns); } sub load_sql_with_null_key { my($self, $key_columns, $key_values, $db) = @_; my $i = 0; my $fq = $self->sql_qualify_column_names_on_load; my $table = $self->table; no warnings; return 'SELECT ' . $self->select_nonlazy_columns_string_sql($db) . ' FROM ' . $self->fq_table_sql($db) . ' WHERE ' . join(' AND ', map { my $c = $self->column($_); ($fq ? $db->auto_quote_column_with_table($c->name_sql, $table) : $c->name_sql($db)) . (defined $key_values->[$i++] ? ' = ' . $c->query_placeholder_sql : ' IS NULL') } @$key_columns); } sub update_all_sql { my($self, $key_columns, $db) = @_; $key_columns ||= $self->primary_key_column_names; my $cache_key = "$db->{'id'}:" . join("\0", @$key_columns); return $self->{'update_all_sql'}{$cache_key} if($self->{'update_all_sql'}{$cache_key}); my %key = map { ($_ => 1) } @$key_columns; no warnings; return $self->{'update_all_sql'}{$cache_key} = 'UPDATE ' . $self->fq_table_sql($db) . " SET \n" . join(",\n", map { ' ' . $_->name_sql($db) . ' = ' . $_->update_placeholder_sql($db) } grep { !$key{$_->name} } $self->columns_ordered) . "\nWHERE " . join(' AND ', map { my $c = $self->column($_); $c->name_sql($db) . ' = ' . $c->query_placeholder_sql } @$key_columns); } use constant LAZY_LOADED_KEY => lazy_column_values_loaded_key(); sub update_sql { my($self, $obj, $key_columns, $db) = @_; $key_columns ||= $self->primary_key_column_names; my %key = map { ($_ => 1) } @$key_columns; no warnings 'uninitialized'; my @columns = grep { !$key{$_->name} && (!$_->lazy || $obj->{LAZY_LOADED_KEY()}{$_->name}) } $self->columns_ordered; my @exec; unless($self->dbi_requires_bind_param($db)) { my $method_name = $self->column_accessor_method_names_hash; foreach my $column (@columns) { my $method = $method_name->{$column->{'name'}}; push(@exec, $obj->$method()); } } return (($self->{'update_sql_prefix'}{$db->{'id'}} || $self->init_update_sql_prefix($db)) . join(",\n", map { ' ' . $_->name_sql($db) . ' = ' . $_->update_placeholder_sql($db) } @columns) . "\nWHERE " . join(' AND ', map { my $c = $self->column($_); $c->name_sql($db) . ' = ' . $c->query_placeholder_sql($db) } @$key_columns), \@exec, \@columns); } sub init_update_sql_prefix { my($self, $db) = @_; return $self->{'update_sql_prefix'}{$db->{'id'}} = 'UPDATE ' . $self->fq_table_sql($db) . " SET \n"; } sub update_changes_only_sql { my($self, $obj, $key_columns, $db) = @_; $key_columns ||= $self->primary_key_column_names; my %key = map { ($_ => 1) } @$key_columns; my @modified = map { $self->column($_) } grep { !$key{$_} } keys %{$obj->{MODIFIED_COLUMNS()} || {}}; return unless(@modified); no warnings; return ($self->{'update_sql_prefix'}{$db->{'id'}} ||= 'UPDATE ' . $self->fq_table_sql($db) . " SET \n") . join(",\n", map { ' ' . $_->name_sql($db) . ' = ' . $_->update_placeholder_sql($db) } @modified) . "\nWHERE " . join(' AND ', map { my $c = $self->column($_); $c->name_sql($db) . ' = ' . $c->query_placeholder_sql($db) } @$key_columns), [ map { my $m = $_->accessor_method_name; $obj->$m() } @modified ], \@modified; } # This is nonsensical right now because the primary key always has to be # non-null, and any update will use the primary key instead of a unique # key. But I'll leave the code here (commented out) just in case. # # sub update_all_sql_with_null_key # { # my($self, $key_columns, $key_values, $db) = @_; # # my %key = map { ($_ => 1) } @$key_columns; # my $i = 0; # # no warnings; # return # 'UPDATE ' . $self->fq_table_sql($db) . " SET \n" . # join(",\n", map { ' ' . $self->column($_)->name_sql($db) . ' = ?' } # grep { !$key{$_} } $self->column_names) . # "\nWHERE " . join(' AND ', map { defined $key_values->[$i++] ? "$_ = ?" : "$_ IS NULL" } # map { $self->column($_)->name_sql($db) } @$key_columns); # } # # Ditto for this version of update_sql_with_inlining which handles null keys # # sub update_sql_with_inlining # { # my($self, $obj, $key_columns, $key_values) = @_; # # my $db = $obj->db or Carp::croak "Missing db"; # # $key_columns ||= $self->primary_key_column_names; # # my %key = map { ($_ => 1) } @$key_columns; # # my @bind; # my @updates; # # foreach my $column (grep { !$key{$_} } $self->columns_ordered) # { # my $method = $self->column_method($column->name); # my $value = $obj->$method(); # # if($column->should_inline_value($db, $value)) # { # push(@updates, ' ' . $column->name_sql($db) . " = $value"); # } # else # { # push(@updates, ' ' . $column->name_sql($db) . ' = ?'); # push(@bind, $value); # } # } # # my $i = 0; # # no warnings; # return # ( # ($self->{'update_sql_with_inlining_start'} ||= # 'UPDATE ' . $self->fq_table_sql($db) . " SET \n") . # join(",\n", @updates) . "\nWHERE " . # join(' AND ', map { defined $key_values->[$i++] ? "$_ = ?" : "$_ IS NULL" } # map { $self->column($_)->name_sql($db) } @$key_columns), # \@bind # ); # } sub update_sql_with_inlining { my($self, $obj, $key_columns) = @_; my $db = $obj->db or Carp::croak "Missing db"; $key_columns ||= $self->primary_key_column_names; my %key = map { ($_ => 1) } @$key_columns; my(@bind, @updates, @bind_params); my $do_bind_params = $self->dbi_requires_bind_param($db); foreach my $column (grep { !$key{$_} && (!$_->{'lazy'} || $obj->{LAZY_LOADED_KEY()}{$_->{'name'}}) } $self->columns_ordered) { my $method = $self->column_accessor_method_name($column->name); my $value = $obj->$method(); if($column->should_inline_value($db, $value)) { push(@updates, $column->name_sql($db) . " = $value"); } else { push(@updates, $column->name_sql($db) . ' = ' . $column->update_placeholder_sql($db)); push(@bind, $value); if($do_bind_params) { push(@bind_params, $column->dbi_bind_param_attrs($db)); } } } my $i = 0; no warnings; return ( ($self->{'update_sql_with_inlining_start'}{$db->{'id'}} || $self->init_update_sql_with_inlining_start($db)) . join(",\n", @updates) . "\nWHERE " . join(' AND ', map { my $c = $self->column($_); $c->name_sql($db) . ' = ' . $c->query_placeholder_sql($db) } @$key_columns), \@bind, ($do_bind_params ? \@bind_params : ()) ); } sub init_update_sql_with_inlining_start { my($self, $db) = @_; return $self->{'update_sql_with_inlining_start'}{$db->{'id'}} = 'UPDATE ' . $self->fq_table_sql($db) . " SET \n"; } sub update_changes_only_sql_with_inlining { my($self, $obj, $key_columns) = @_; my $db = $obj->db or Carp::croak "Missing db"; $key_columns ||= $self->primary_key_column_names; my %key = map { ($_ => 1) } @$key_columns; my $modified = $obj->{MODIFIED_COLUMNS()}; my(@bind, @updates, @bind_params); my $do_bind_params = $self->dbi_requires_bind_param($db); foreach my $column (grep { !$key{$_->{'name'}} && $modified->{$_->{'name'}} } $self->columns_ordered) { my $method = $self->column_accessor_method_name($column->name); my $value = $obj->$method(); if($column->should_inline_value($db, $value)) { push(@updates, ' ' . $column->name_sql($db) . " = $value"); } else { push(@updates, $column->name_sql($db) . ' = ' . $column->update_placeholder_sql($db)); push(@bind, $value); if($do_bind_params) { push(@bind_params, $column->dbi_bind_param_attrs($db)); } } } return unless(@updates); my $i = 0; no warnings; return ( ($self->{'update_sql_with_inlining_start'}{$db->{'id'}} ||= 'UPDATE ' . $self->fq_table_sql($db) . " SET \n") . join(",\n", @updates) . "\nWHERE " . join(' AND ', map { my $c = $self->column($_); $c->name_sql($db) . ' = ' . $c->query_placeholder_sql($db) } @$key_columns), \@bind, ($do_bind_params ? \@bind_params : ()) ); } sub insert_sql { my($self, $db) = @_; no warnings; return $self->{'insert_sql'}{$db->{'id'}} ||= 'INSERT INTO ' . $self->fq_table_sql($db) . "\n(\n" . join(",\n", map { " $_" } $self->column_names_sql($db)) . "\n)\nVALUES\n(\n" . $self->insert_columns_placeholders_sql($db) . "\n)"; } sub insert_changes_only_sql { my($self, $obj, $db) = @_; my $modified = $obj->{MODIFIED_COLUMNS()} || {}; my @modified = grep { $modified->{$_->{'name'}} || $_->default_exists } $self->columns_ordered; unless(@modified) { # Make a last-ditch attempt to insert with no modified columns # using the DEFAULT keyword on an arbitrary column. This works # in MySQL and PostgreSQL. if($db->supports_arbitrary_defaults_on_insert) { return 'INSERT INTO ' . $self->fq_table_sql($db) . ' (' . ($self->columns_ordered)[-1]->name_sql($db) . ') VALUES (DEFAULT)', []; } else { Carp::croak "Cannot insert row into table '", $self->table, "' - No columns have modified or default values"; } } no warnings; return ($self->{'insert_changes_only_sql_prefix'}{$db->{'id'}} || $self->init_insert_changes_only_sql_prefix($db)) . join(",\n", map { $_->name_sql($db) } @modified) . "\n)\nVALUES\n(\n" . join(",\n", map { $_->insert_placeholder_sql($db) } @modified) . "\n)", [ map { my $m = $_->accessor_method_name; $obj->$m() } @modified ], \@modified; } sub init_insert_changes_only_sql_prefix { my($self, $db) = @_; return $self->{'insert_changes_only_sql_prefix'}{$db->{'id'}} = 'INSERT INTO ' . $self->fq_table_sql($db) . "\n(\n"; ; } sub insert_columns_placeholders_sql { my($self, $db) = @_; return $self->{'insert_columns_placeholders_sql'}{$db->{'id'}} ||= join(",\n", map { ' ' . $_->insert_placeholder_sql($db) } $self->columns_ordered) } sub insert_and_on_duplicate_key_update_sql { my($self, $obj, $db, $changes_only) = @_; my(@columns, @names, @bind); if($obj->{STATE_IN_DB()}) { my %seen; @columns = $changes_only ? (map { $self->column($_) } grep { !$seen{$_}++ } ($self->primary_key_column_names, keys %{$obj->{MODIFIED_COLUMNS()} || {}})) : (grep { (!$_->{'lazy'} || $obj->{LAZY_LOADED_KEY()}{$_->{'name'}}) } $self->columns_ordered); @names = map { $_->name_sql($db) } @columns; foreach my $column (@columns) { my $method = $self->column_accessor_method_name($column->{'name'}); push(@bind, $obj->$method()); } } else { my %skip; my @key_columns = $self->primary_key_column_names; my @key_methods = $self->primary_key_column_accessor_names; my @key_values = grep { defined } map { $obj->$_() } @key_methods; unless(@key_values) { @skip{@key_columns} = (1) x @key_columns; } foreach my $uk ($self->unique_keys) { @key_columns = $uk->columns; @key_methods = map { $_->accessor_method_name } @key_columns; @key_values = grep { defined } map { $obj->$_() } @key_methods; unless(@key_values) { @skip{@key_columns} = (1) x @key_columns; } } @columns = $changes_only ? (map { $self->column($_) } grep { !$skip{"$_"} } keys %{$obj->{MODIFIED_COLUMNS()} || {}}) : (grep { !$skip{"$_"} && (!$_->{'lazy'} || $obj->{LAZY_LOADED_KEY()}{$_->{'name'}}) } $self->columns_ordered); @names = map { $_->name_sql($db) } @columns; foreach my $column (@columns) { my $method = $self->column_accessor_method_name($column->{'name'}); push(@bind, $obj->$method()); } } no warnings; return 'INSERT INTO ' . $self->fq_table_sql($db) . "\n(\n" . join(",\n", @names) . "\n)\nVALUES\n(\n" . join(",\n", map { $_->insert_placeholder_sql($db) } @columns) . "\n)\nON DUPLICATE KEY UPDATE\n" . join(",\n", map { $_->name_sql($db) . ' = ' . $_->update_placeholder_sql($db) } @columns), [ @bind, @bind ], [ @columns, @columns ]; } sub insert_sql_with_inlining { my($self, $obj) = @_; my $db = $obj->db or Carp::croak "Missing db"; my(@bind, @places, @bind_params); my $do_bind_params = $self->dbi_requires_bind_param($db); foreach my $column ($self->columns_ordered) { my $method = $self->column_accessor_method_name($column->name); my $value = $obj->$method(); if($column->should_inline_value($db, $value)) { push(@places, " $value"); } else { push(@places, $column->insert_placeholder_sql($db)); push(@bind, $value); if($do_bind_params) { push(@bind_params, $column->dbi_bind_param_attrs($db)); } } } return ( ($self->{'insert_sql_with_inlining_start'}{$db->{'id'}} || $self->init_insert_sql_with_inlining_start($db)) . join(",\n", @places) . "\n)", \@bind, ($do_bind_params ? \@bind_params : ()) ); } sub init_insert_sql_with_inlining_start { my($self, $db) = @_; $self->{'insert_sql_with_inlining_start'}{$db->{'id'}} = 'INSERT INTO ' . $self->fq_table_sql($db) . "\n(\n" . join(",\n", map { " $_" } $self->column_names_sql($db)) . "\n)\nVALUES\n(\n"; } sub insert_and_on_duplicate_key_update_with_inlining_sql { my($self, $obj, $db, $changes_only) = @_; my(@columns, @names); my $do_bind_params = $self->dbi_requires_bind_param($db); if($obj->{STATE_IN_DB()}) { my %seen; @columns = $changes_only ? (map { $self->column($_) } grep { !$seen{$_}++ } ($self->primary_key_column_names, keys %{$obj->{MODIFIED_COLUMNS()} || {}})) : (grep { (!$_->{'lazy'} || $obj->{LAZY_LOADED_KEY()}{$_->{'name'}}) } $self->columns_ordered); @names = map { $_->name_sql($db) } @columns; } else { my %skip; my @key_columns = $self->primary_key_column_names; my @key_methods = $self->primary_key_column_accessor_names; my @key_values = grep { defined } map { $obj->$_() } @key_methods; unless(@key_values) { @skip{@key_columns} = (1) x @key_columns; } foreach my $uk ($self->unique_keys) { @key_columns = $uk->columns; @key_methods = map { $_->accessor_method_name } @key_columns; @key_values = grep { defined } map { $obj->$_() } @key_methods; unless(@key_values) { @skip{@key_columns} = (1) x @key_columns; } } @columns = $changes_only ? (map { $self->column($_) } grep { !$skip{"$_"} } keys %{$obj->{MODIFIED_COLUMNS()} || {}}) : (grep { !$skip{"$_"} && (!$_->{'lazy'} || $obj->{LAZY_LOADED_KEY()}{$_->{'name'}}) } $self->columns_ordered); @names = map { $_->name_sql($db) } @columns; } my(@bind, @places, @bind_params); foreach my $column (@columns) { my $name = $column->{'name'}; my $method = $self->column_accessor_method_name($name); my $value = $obj->$method(); if($column->should_inline_value($db, $value)) { push(@places, [ $name, $column->inline_value_sql($value) ]); } else { push(@places, [ $name, $column->insert_placeholder_sql($_) ]); push(@bind, $value); if($do_bind_params) { push(@bind_params, $column->dbi_bind_param_attrs($db)); } } } no warnings; return 'INSERT INTO ' . $self->fq_table_sql($db) . "\n(\n" . join(",\n", @names) . "\n)\nVALUES\n(\n" . join(",\n", map { $_->[1] } @places) . "\n)\n" . "ON DUPLICATE KEY UPDATE\n" . join(",\n", map { "$_->[0] = $_->[1]" } @places), [ @bind, @bind ], ($do_bind_params ? \@bind_params : ()); } sub insert_changes_only_sql_with_inlining { my($self, $obj) = @_; my $db = $obj->db or Carp::croak "Missing db"; my $modified = $obj->{MODIFIED_COLUMNS()} || {}; my @modified = grep { $modified->{$_->{'name'}} || $_->default_exists } $self->columns_ordered; unless(@modified) { # Make a last-ditch attempt to insert with no modified columns # using the DEFAULT keyword on an arbitrary column. This works # in MySQL and PostgreSQL. if($db->supports_arbitrary_defaults_on_insert) { return 'INSERT INTO ' . $self->fq_table_sql($db) . ' (' . ($self->columns_ordered)[-1]->name_sql($db) . ') VALUES (DEFAULT)', []; } else { Carp::croak "Cannot insert row into table '", $self->table, "' - No columns have modified or default values"; } } my(@bind, @places, @bind_params); my $do_bind_params = $self->dbi_requires_bind_param($db); foreach my $column (@modified) { my $method = $self->column_accessor_method_name($column->name); my $value = $obj->$method(); if($column->should_inline_value($db, $value)) { push(@places, " $value"); } else { push(@places, $column->insert_placeholder_sql($db)); push(@bind, $value); if($do_bind_params) { push(@bind_params, $column->dbi_bind_param_attrs($db)); } } } return ( 'INSERT INTO ' . $self->fq_table_sql($db) . "\n(\n" . join(",\n", map { $_->name_sql($db) } @modified) . "\n)\nVALUES\n(\n" . join(",\n", @places) . "\n)", \@bind, ($do_bind_params ? \@bind_params : ()) ); } sub delete_sql { my($self, $db) = @_; return $self->{'delete_sql'}{$db->{'id'}} ||= 'DELETE FROM ' . $self->fq_table_sql($db) . ' WHERE ' . join(' AND ', map { $_->name_sql($db) . ' = ' . $_->query_placeholder_sql($db) } $self->primary_key_columns); } sub get_column_value { my($self, $object, $column) = @_; my $db = $object->db or Carp::confess $object->error; my $dbh = $db->dbh or Carp::confess $db->error; my $sql = $self->{'get_column_sql_tmpl'}{$db->{'id'}} || $self->init_get_column_sql_tmpl($db); $sql =~ s/__COLUMN__/$column->name_sql($db)/e; my @key_values = map { $object->$_() } map { $self->column_accessor_method_name($_) } $self->primary_key_column_names; my($value, $error); TRY: { local $@; eval { ($Debug || $Rose::DB::Object::Debug) && warn "$sql (@key_values)\n"; my $sth = $dbh->prepare($sql); $sth->execute(@key_values); $sth->bind_columns(\$value); $sth->fetch; }; $error = $@; } if($error) { Carp::croak "Could not lazily-load column value for column '", $column->name, "' - $error"; } return $value; } sub init_get_column_sql_tmpl { my($self, $db) = @_; my $key_columns = $self->primary_key_column_names; my %key = map { ($_ => 1) } @$key_columns; return $self->{'get_column_sql_tmpl'}{$db->{'id'}} = 'SELECT __COLUMN__ FROM ' . $self->fq_table_sql($db) . ' WHERE ' . join(' AND ', map { my $c = $self->column($_); $c->name_sql($db) . ' = ' . $c->query_placeholder_sql($db) } @$key_columns); } sub refresh_lazy_column_tracking { my($self) = shift; $self->_clear_column_generated_values; # Initialize method name hashes $self->column_accessor_method_names; $self->column_mutator_method_names; $self->column_rw_method_names; return $self->{'has_lazy_columns'} = grep { $_->lazy } $self->columns_ordered; } sub has_lazy_columns { my($self) = shift; return $self->{'has_lazy_columns'} if(defined $self->{'has_lazy_columns'}); return $self->{'has_lazy_columns'} = grep { $_->lazy } $self->columns_ordered; } sub prime_all_caches { my($class) = shift; foreach my $obj_class ($class->registered_classes) { $obj_class->meta->prime_caches(@_); } } sub prime_caches { my($self, %args) = @_; my @methods = qw(column_names num_columns nonlazy_column_names lazy_column_names column_rw_method_names column_accessor_method_names nonlazy_column_accessor_method_names column_mutator_method_names nonlazy_column_mutator_method_names nonlazy_column_db_value_hash_keys primary_key_column_db_value_hash_keys column_db_value_hash_keys column_accessor_method_names column_mutator_method_names column_rw_method_names key_column_accessor_method_names_hash); foreach my $method (@methods) { $self->$method(); } my $db = $args{'db'} || $self->class->init_db; $self->method_column('nonesuch'); $self->fq_primary_key_sequence_names(db => $db); @methods = qw(dbi_requires_bind_param fq_table fq_table_sql init_get_column_sql_tmpl delete_sql primary_key_sequence_names insert_sql init_insert_sql_with_inlining_start init_insert_changes_only_sql_prefix init_update_sql_prefix init_update_sql_with_inlining_start column_names_string_sql nonlazy_column_names_string_sql select_nonlazy_columns_string_sql select_columns_string_sql select_columns_sql select_nonlazy_columns_sql); foreach my $method (@methods) { $self->$method($db); } undef @methods; # reclaim memory? foreach my $key ($self->primary_key, $self->unique_keys) { foreach my $method (qw(update_all_sql load_sql load_all_sql)) { $self->$method(scalar $key->columns, $db); } } } sub _clear_table_generated_values { my($self) = shift; $self->{'fq_table'} = undef; $self->{'fq_table_sql'} = undef; $self->{'get_column_sql_tmpl'} = undef; $self->{'load_sql'} = undef; $self->{'load_all_sql'} = undef; $self->{'delete_sql'} = undef; $self->{'fq_primary_key_sequence_names'} = undef; $self->{'primary_key_sequence_names'} = undef; $self->{'insert_sql'} = undef; $self->{'insert_sql_with_inlining_start'} = undef; $self->{'insert_changes_only_sql_prefix'} = undef; $self->{'update_sql_prefix'} = undef; $self->{'update_sql_with_inlining_start'} = undef; $self->{'update_all_sql'} = undef; } sub _clear_column_generated_values { my($self) = shift; $self->{'fq_table'} = undef; $self->{'fq_table_sql'} = undef; $self->{'column_names'} = undef; $self->{'num_columns'} = undef; $self->{'nonlazy_column_names'} = undef; $self->{'lazy_column_names'} = undef; $self->{'column_names_sql'} = undef; $self->{'get_column_sql_tmpl'} = undef; $self->{'column_names_string_sql'} = undef; $self->{'nonlazy_column_names_string_sql'} = undef; $self->{'column_rw_method_names'} = undef; $self->{'column_accessor_method_names'} = undef; $self->{'nonlazy_column_accessor_method_names'} = undef; $self->{'column_mutator_method_names'} = undef; $self->{'nonlazy_column_mutator_method_names'} = undef; $self->{'nonlazy_column_db_value_hash_keys'} = undef; $self->{'primary_key_column_db_value_hash_keys'}= undef; $self->{'primary_key_column_names_or_aliases'} = undef; $self->{'column_db_value_hash_keys'} = undef; $self->{'select_nonlazy_columns_string_sql'} = undef; $self->{'select_columns_string_sql'} = undef; $self->{'select_columns_sql'} = undef; $self->{'select_nonlazy_columns_sql'} = undef; $self->{'method_columns'} = undef; $self->{'column_accessor_method'} = undef; $self->{'key_column_accessor_method'} = undef; $self->{'column_rw_method'} = undef; $self->{'load_sql'} = undef; $self->{'load_all_sql'} = undef; $self->{'update_all_sql'} = undef; $self->{'update_sql_prefix'} = undef; $self->{'insert_sql'} = undef; $self->{'insert_sql_with_inlining_start'} = undef; $self->{'update_sql_with_inlining_start'} = undef; $self->{'insert_changes_only_sql_prefix'} = undef; $self->{'delete_sql'} = undef; $self->{'insert_columns_placeholders_sql'} = undef; $self->{'dbi_requires_bind_param'} = undef; $self->{'key_column_names'} = undef; } sub _clear_nonpersistent_column_generated_values { my($self) = shift; $self->{'nonpersistent_column_names'} = undef; $self->{'nonpersistent_column_accessor_method_names'} = undef; $self->{'nonpersistent_column_accessor_method'} = undef; $self->{'nonpersistent_column_mutator_method_names'} = undef; $self->{'nonpersistent_column_mutator_method'} = undef; } sub _clear_primary_key_column_generated_values { my($self) = shift; $self->{'primary_key_column_accessor_names'} = undef; $self->{'primary_key_column_mutator_names'} = undef; $self->{'key_column_accessor_method'} = undef; $self->{'primary_key_column_names_or_aliases'} = undef; $self->{'key_column_names'} = undef; } sub method_name_is_reserved { my($self, $name, $class) = @_; if(!defined $class && UNIVERSAL::isa($self, __PACKAGE__)) { $class ||= $self->class or die "Missing class!"; } Carp::confess "Missing method name argument in call to method_name_is_reserved()" unless(defined $name); if(index($name, PRIVATE_PREFIX) == 0) { return "The method prefix '", PRIVATE_PREFIX, "' is reserved." } elsif($name =~ /^(?:meta|dbh?|_?init_db|error|not_found|load|save|update|insert|delete|DESTROY)$/ || ($class->isa('Rose::DB::Object::Cached') && $name =~ /^(?:remember|forget(?:_all)?)$/)) { return "This method name is reserved for use by the $class API." } return 0; } sub method_name_from_column_name { my($self, $column_name, $method_type) = @_; my $column = $self->column($column_name) || $self->nonpersistent_column($column_name) or Carp::confess "No such column: $column_name"; return $self->method_name_from_column($column, $method_type); } sub method_name_from_column { my($self, $column, $method_type) = @_; my $default_name = $column->build_method_name_for_type($method_type); my $method_name = $column->method_name($method_type) || $self->convention_manager->auto_column_method_name($method_type, $column, $default_name, $self->class) || $default_name; if(my $code = $self->column_name_to_method_name_mapper) { my $column_name = $column->name; local $_ = $method_name; $method_name = $code->($self, $column_name, $method_type, $method_name); unless(defined $method_name) { Carp::croak "column_name_to_method_name_mapper() returned undef ", "for column name '$column_name' method type '$method_type'" } } return $method_name; } sub dbi_requires_bind_param { my($self, $db) = @_; return $self->{'dbi_requires_bind_param'}{$db->{'id'}} if(defined $self->{'dbi_requires_bind_param'}{$db->{'id'}}); foreach my $column ($self->columns_ordered) { if($column->dbi_requires_bind_param($db)) { return $self->{'dbi_requires_bind_param'}{$db->{'id'}} = 1; } } return $self->{'dbi_requires_bind_param'}{$db->{'id'}} = 0; } sub make_manager_class { my($self) = shift; my $error; TRY: { local $@; eval { eval $self->perl_manager_class(@_) }; $error = $@; } if($error) { Carp::croak "Could not make manager class - $error\nThe Perl code used was:\n\n", $self->perl_manager_class(@_); } } sub perl_manager_class { my($self) = shift; my %args; if(@_ == 1) { $args{'base_name'} = shift; } else { %args = @_; } $args{'base_name'} ||= $self->convention_manager->auto_manager_base_name; $args{'class'} ||= $self->convention_manager->auto_manager_class_name; unless($args{'class'} =~ /^\w+(?:::\w+)*$/) { no warnings; Carp::croak "Missing or invalid class", (length $args{'class'} ? ": '$args{'class'}'" : ''); } unless($args{'isa'}) { my @def = $self->default_manager_base_class; # may return multiple classes $args{'isa'} = (@def == 1 && ref $def[0]) ? $def[0] : \@def; } $args{'isa'} = [ $args{'isa'} ] unless(ref $args{'isa'}); my($isa, $ok); foreach my $class (@{$args{'isa'}}) { unless($class =~ /^\w+(?:::\w+)*$/) { no warnings; Carp::croak "Invalid isa class: '$class'"; } no strict 'refs'; $isa .= "use $class;\n" unless($class !~ /^Rose::DB::/ && %{"${class}::"}); $ok = 1 if(UNIVERSAL::isa($class, 'Rose::DB::Object::Manager')); } unless($ok) { Carp::croak "None of these classes inherit from Rose::DB::Object::Manager: ", join(', ', @{$args{'isa'}}); } $isa .= "our \@ISA = qw(@{$args{'isa'}});"; no strict 'refs'; if(@{"$args{'class'}::ISA"}) { Carp::croak "Can't override class $args{'class'} which already ", "appears to be defined."; } my $object_class = $self->class; return<<"EOF"; package $args{'class'}; use strict; $isa sub object_class { '$object_class' } __PACKAGE__->make_manager_methods('$args{'base_name'}'); 1; EOF } # # Automatic metadata setup # our $AUTOLOAD; sub DESTROY { } sub AUTOLOAD { if($AUTOLOAD =~ /::((?:auto_(?!helper)|(?:default_)?perl_)\w*)$/) { my $method = $1; my $self = shift; $self->init_auto_helper; unless($self->can($method)) { Carp::croak "No such method '$method' in class ", ref($self); } return $self->$method(@_); } Carp::confess "No such method: $AUTOLOAD"; } sub auto_helper_class { my($self) = shift; if(@_) { my $driver = lc shift; return $self->auto_helper_classes->{$driver} = shift if(@_); return $self->auto_helper_classes->{$driver}; } else { my $db = $self->db or die "Missing db"; return $self->auto_helper_classes->{$db->driver} || $self->auto_helper_classes->{'generic'} || Carp::croak "Don't know how to auto-initialize using driver '", $db->driver, "'"; } } my %Rebless; sub init_auto_helper { my($self) = shift; unless($self->isa($self->auto_helper_class)) { my $class = ref($self) || $self; my $auto_helper_class = $self->auto_helper_class; no strict 'refs'; unless(@{"${auto_helper_class}::ISA"}) { my $error; TRY: { local $@; eval "use $auto_helper_class"; $error = $@; } Carp::croak "Could not load '$auto_helper_class' - $error" if($error); } $self->original_class($class); REBLESS: # Do slightly evil re-blessing magic { # Check cache if(my $new_class = $Rebless{$class,$auto_helper_class}) { bless $self, $new_class; } else { # Special, simple case for Rose::DB::Object::Metadata if($class eq __PACKAGE__) { bless $self, $auto_helper_class; } else # Handle Rose::DB::Object::Metadata subclasses { # If this is a default Rose::DB driver class if(index($auto_helper_class, 'Rose::DB::') == 0) { # Make a new metadata class based on the current class my $new_class = $class . '::__RoseDBObjectMetadataPrivate__::' . $auto_helper_class; # Pull all the auto-helper's methods up into the new class, # unless they're already defined by the original class. This # is ugly, I know, but remember that it's all an implementation # detail that could change at any time :) IMPORT: { no strict 'refs'; local(*auto_symbol, *existing_symbol); while(my($name, $value) = each(%{"${auto_helper_class}::"})) { no warnings; next if($name =~ /^[A-Z]+$/); # skip BEGIN, DESTROY, etc. *auto_symbol = $value; *existing_symbol = *{"${class}::$name"}; if(defined &auto_symbol && !defined &existing_symbol) { $Debug && warn "IMPORT $name INTO $new_class FROM $auto_helper_class\n"; *{"${new_class}::$name"} = \&auto_symbol; } } } no strict 'refs'; @{"${new_class}::ISA"} = ($class, $auto_helper_class); bless $self, $new_class; } else { # Otherwise use the (apparently custom) metadata class bless $self, $auto_helper_class; } } # Cache value $Rebless{$class,$auto_helper_class} = ref $self; } } } return 1; } sub map_record_method_key { my($self, $method) = (shift, shift); if(@_) { return $self->{'map_record_method_key'}{$method} = shift; } return $self->{'map_record_method_key'}{$method}; } sub column_undef_overrides_default { my($self) = shift; if(@_) { return $self->{'column_undef_overrides_default'} = $_[0] ? 1 : 0; } return $self->{'column_undef_overrides_default'} if(defined $self->{'column_undef_overrides_default'}); return $self->{'column_undef_overrides_default'} = ref($self)->default_column_undef_overrides_default; } 1; __END__ =head1 NAME Rose::DB::Object::Metadata - Database object metadata. =head1 SYNOPSIS use Rose::DB::Object::Metadata; $meta = Rose::DB::Object::Metadata->new(class => 'Product'); # ...or... $meta = Rose::DB::Object::Metadata->for_class('Product'); # # Auto-initialization # $meta->table('products'); # optional if class name ends with "::Product" $meta->auto_initialize; # # ...or manual setup # $meta->setup ( table => 'products', columns => [ id => { type => 'int', primary_key => 1 }, name => { type => 'varchar', length => 255 }, description => { type => 'text' }, category_id => { type => 'int' }, status => { type => 'varchar', check_in => [ 'active', 'inactive' ], default => 'inactive', }, start_date => { type => 'datetime' }, end_date => { type => 'datetime' }, date_created => { type => 'timestamp', default => 'now' }, last_modified => { type => 'timestamp', default => 'now' }, ], unique_key => 'name', foreign_keys => [ category => { class => 'Category', key_columns => { category_id => 'id', } }, ], relationships => [ prices => { type => 'one to many', class => 'Price', column_map => { id => 'id_product' }, }, ], ); # # ...or even more verbose manual setup (old-style, not recommended) # $meta->table('products'); $meta->columns ( id => { type => 'int', primary_key => 1 }, name => { type => 'varchar', length => 255 }, description => { type => 'text' }, category_id => { type => 'int' }, status => { type => 'varchar', check_in => [ 'active', 'inactive' ], default => 'inactive', }, start_date => { type => 'datetime' }, end_date => { type => 'datetime' }, date_created => { type => 'timestamp', default => 'now' }, last_modified => { type => 'timestamp', default => 'now' }, ); $meta->unique_key('name'); $meta->foreign_keys ( category => { class => 'Category', key_columns => { category_id => 'id', } }, ); $meta->relationships ( prices => { type => 'one to many', class => 'Price', column_map => { id => 'id_product' }, }, ); ... =head1 DESCRIPTION L objects store information about a single table in a database: the name of the table, the names and types of columns, any foreign or unique keys, etc. These metadata objects are also responsible for supplying information to, and creating object methods for, the L-derived objects to which they belong. L objects also store information about the Ls that front the database tables they describe. What might normally be thought of as "class data" for the L is stored in the metadata object instead, in order to keep the method namespace of the L-derived class uncluttered. L objects are per-class singletons; there is one L object for each L-derived class. Metadata objects are almost never explicitly instantiated. Rather, there are automatically created and accessed through L-derived objects' L method. Once created, metadata objects can be populated manually or automatically. Both techniques are shown in the L above. The automatic mode works by asking the database itself for the information. There are some caveats to this approach. See the L section for more information. L objects contain three categories of objects that are responsible for creating object methods in L-derived classes: columns, foreign keys, and relationships. Column objects are subclasses of L. They are intended to store as much information as possible about each column. The particular class of the column object created for a database column is determined by a L. The column class, in turn, is responsible for creating the accessor/mutator method(s) for the column. When it creates these methods, the column class can use (or ignore) any information stored in the column object. Foreign key objects are of the class L. They store information about columns that refer to columns in other tables that are fronted by their own L-derived classes. A foreign key object is responsible for creating accessor method(s) to fetch the foreign object from the foreign table. Relationship objects are subclasses of L. They store information about a table's relationship to other tables that are fronted by their own L-derived classes. The particular class of the relationship object created for each relationship is determined by a L. A relationship object is responsible for creating accessor method(s) to fetch the foreign objects from the foreign table. =head1 AUTO-INITIALIZATION Manual population of metadata objects can be tedious and repetitive. Nearly all of the information stored in a L object exists in the database in some form. It's reasonable to consider simply extracting this information from the database itself, rather than entering it all manually. This automatic metadata extraction and subsequent L object population is called "auto-initialization." The example of auto-initialization in the L above is the most succinct variant: $meta->auto_initialize; As you can read in the documentation for the L method, that's shorthand for individually auto-initializing each part of the metadata object: columns, the primary key, unique keys, and foreign keys. But this brevity comes at a price. There are many caveats to auto-initialization. =head2 Caveats =head3 Start-Up Cost In order to retrieve the information required for auto-initialization, a database connection must be opened and queries must be run. Sometimes these queries include complex joins. All of these queries must be successfully completed before the L-derived objects that the L is associated with can be used. In an environment like L, server start-up time is precisely when you want to do any expensive operations. But in a command-line script or other short-lived process, the overhead of auto-initializing many metadata objects may become prohibitive. Also, don't forget that auto-initialization requires a database connection. L-derived objects can sometimes be useful even without a database connection (e.g., to temporarily store information that will never go into the database, or to synthesize data using object methods that have no corresponding database column). When using auto-initialization, this is not possible because the L-derived class won't even load if auto-initialization fails because it could not connect to the database. =head3 Detail First, auto-initialization cannot generate information that exists only in the mind of the programmer. The most common example is a relationship between two database tables that is either ambiguous or totally unexpressed by the database itself. For example, if a foreign key constraint does not exist, the relationship between rows in two different tables cannot be extracted from the database, and therefore cannot be auto-initialized. Even within the realm of information that, by all rights, should be available in the database, there are limitations. Although there is a handy L API for extracting metadata from databases, unfortunately, very few DBI drivers support it fully. Some don't support it at all. In almost all cases, some manual work is required to (often painfully) extract information from the database's "system tables" or "catalog." More troublingly, databases do not always provide all the metadata that a human could extract from the series of SQL statement that created the table in the first place. Sometimes, the information just isn't in the database to be extracted, having been lost in the process of table creation. Here's just one example. Consider this MySQL table definition: CREATE TABLE mytable ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, code CHAR(6), flag BOOLEAN NOT NULL DEFAULT 1, bits BIT(5) NOT NULL DEFAULT '00101', name VARCHAR(64) ); Now look at the metadata that MySQL 4 stores internally for this table: mysql> describe mytable; +-------+------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+------------------+------+-----+---------+----------------+ | id | int(10) unsigned | | PRI | NULL | auto_increment | | code | varchar(6) | YES | | NULL | | | flag | tinyint(1) | | | 1 | | | bits | tinyint(1) | | | 101 | | | name | varchar(64) | YES | | NULL | | +-------+------------------+------+-----+---------+----------------+ Note the following divergences from the "CREATE TABLE" statement. =over 4 =item * B This is troublesome if you want the traditional semantics of a CHAR type, namely the padding with spaces of values that are less than the column length. =item * B The default accessor method created for boolean columns has value coercion and formatting properties that are important to this data type. The default accessor created for integer columns lacks these constraints. The metadata object has no way of knowing that "flag" was supposed to be a boolean column, and thus makes the wrong kind of accessor method. It is thus possible to store, say, a value of "7" in the "flag" column. Oops. =item * B As in the case of the "flag" column above, this type change prevents the correct accessor method from being created. The default bitfield accessor method auto-inflates column values into L objects, which provide convenient methods for bit manipulation. The default accessor created for integer columns does no such thing. =back Remember that the auto-initialization process can only consider the metadata actually stored in the database. It has no access to the original "create table" statement. Thus, the semantics implied by the original table definition are effectively lost. Again, this is just one example of the kind of detail that can be lost in the process of converting your table definition into metadata that is stored in the database. Admittedly, MySQL is perhaps the worst case-scenario, having a well-deserved reputation for disregarding the wishes of table definitions. (The use of implicit default values for "NOT NULL" columns is yet another example.) Thankfully, there is a solution to this dilemma. Remember that auto-initialization is actually a multi-step process hiding behind that single call to the L method. To correct the sins of the database, simply break the auto-initialization process into its components. For example, here's how to correctly auto-initialize the "mytable" example above: # Make a first pass at column setup $meta->auto_init_columns; # Account for inaccuracies in DBD::mysql's column info by # replacing incorrect column definitions with new ones. # Fix CHAR(6) column that shows up as VARCHAR(6) $meta->column(code => { type => 'char', length => 6 }); # Fix BIT(5) column that shows up as TINYINT(1) $meta->column(bits => { type => 'bits', bits => 5, default => 101 }); # Fix BOOLEAN column that shows up as TINYINT(1) $meta->column(flag => { type => 'boolean', default => 1 }); # Do everything else $meta->auto_initialize; Note that L was called at the end. Without the C parameter, this call will preserve any existing metadata, rather than overwriting it, so our "corrections" are safe. =head3 Maintenance The price of auto-initialization is eternal vigilance. "What does that mean? Isn't auto-initialization supposed to save time and effort?" Well, yes, but at a cost. In addition to the caveats described above, consider what happens when a table definition changes. "Ah ha!" you say, "My existing class will automatically pick up the changes the next time it's loaded! Auto-initialization at its finest!" But is it? What if you added a "NOT NULL" column with no default value? Yes, your existing auto-initialized class will pick up the change, but your existing code will no longer be able to L one these objects. Or what if you're using MySQL and your newly added column is one of the types described above that requires manual tweaking in order to get the desired semantics. Will you always remember to make this change? Auto-initialization is not a panacea. Every time you make a change to your database schema, you must also revisit each affected L-derived class to at least consider whether or not the metadata needs to be corrected or updated. The trade-off may be well worth it, but it's still something to think about. There is, however, a hybrid solution that might be even better. Continue on to the next section to learn more. =head2 Code Generation As described in the L
, auto-initializing metadata at runtime by querying the database has many caveats. An alternate approach is to query the database for metadata just once, and then generate the equivalent Perl code which can be pasted directly into the class definition in place of the call to L. Like the auto-initialization process itself, perl code generation has a convenient wrapper method as well as separate methods for the individual parts. All of the perl code generation methods begin with "perl_", and they support some rudimentary code formatting options to help the code conform to you preferred style. Examples can be found with the documentation for each perl_* method. This hybrid approach to metadata population strikes a good balance between upfront effort and ongoing maintenance. Auto-generating the Perl code for the initial class definition saves a lot of tedious typing. From that point on, manually correcting and maintaining the definition is a small price to pay for the decreased start-up cost, the ability to use the class in the absence of a database connection, and the piece of mind that comes from knowing that your class is stable, and won't change behind your back in response to an "action at a distance" (i.e., a database schema update). =head1 CLASS METHODS =over 4 =item B Get or set a boolean value that indicates whether or not the L method will be called from within the L method. The default is true if the C environment variable (C<$ENV{'MOD_PERL'}>) is set to a true value, false otherwise. =item B Clears the L attribute of the metadata object for each L. =item B Given the column type string TYPE, return the name of the L-derived class used to store metadata and create the accessor method(s) for columns of that type. If a CLASS is passed, the column type TYPE is mapped to CLASS. In both cases, the TYPE argument is automatically converted to lowercase. =item B Get or set the hash that maps column type strings to the names of the L-derived classes used to store metadata and create accessor method(s) for columns of that type. This hash is class data. If you want to modify it, I suggest making your own subclass of L and then setting that as the L of your L subclass. If passed MAP (a list of type/class pairs or a reference to a hash of the same) then MAP replaces the current column type mapping. Returns a list of type/class pairs (in list context) or a reference to the hash of type/class mappings (in scalar context). The default mapping of type names to class names is: scalar => Rose::DB::Object::Metadata::Column::Scalar char => Rose::DB::Object::Metadata::Column::Character character => Rose::DB::Object::Metadata::Column::Character varchar => Rose::DB::Object::Metadata::Column::Varchar varchar2 => Rose::DB::Object::Metadata::Column::Varchar nvarchar => Rose::DB::Object::Metadata::Column::Varchar nvarchar2 => Rose::DB::Object::Metadata::Column::Varchar string => Rose::DB::Object::Metadata::Column::Varchar text => Rose::DB::Object::Metadata::Column::Text blob => Rose::DB::Object::Metadata::Column::Blob bytea => Rose::DB::Object::Metadata::Column::Pg::Bytea bits => Rose::DB::Object::Metadata::Column::Bitfield bitfield => Rose::DB::Object::Metadata::Column::Bitfield bool => Rose::DB::Object::Metadata::Column::Boolean boolean => Rose::DB::Object::Metadata::Column::Boolean int => Rose::DB::Object::Metadata::Column::Integer integer => Rose::DB::Object::Metadata::Column::Integer tinyint => Rose::DB::Object::Metadata::Column::Integer smallint => Rose::DB::Object::Metadata::Column::Integer mediumint => Rose::DB::Object::Metadata::Column::Integer bigint => Rose::DB::Object::Metadata::Column::BigInt serial => Rose::DB::Object::Metadata::Column::Serial bigserial => Rose::DB::Object::Metadata::Column::BigSerial enum => Rose::DB::Object::Metadata::Column::Enum num => Rose::DB::Object::Metadata::Column::Numeric numeric => Rose::DB::Object::Metadata::Column::Numeric decimal => Rose::DB::Object::Metadata::Column::Numeric float => Rose::DB::Object::Metadata::Column::Float float8 => Rose::DB::Object::Metadata::Column::DoublePrecision 'double precision' => Rose::DB::Object::Metadata::Column::DoublePrecision time => Rose::DB::Object::Metadata::Column::Time interval => Rose::DB::Object::Metadata::Column::Interval date => Rose::DB::Object::Metadata::Column::Date datetime => Rose::DB::Object::Metadata::Column::Datetime timestamp => Rose::DB::Object::Metadata::Column::Timestamp timestamptz => Rose::DB::Object::Metadata::Column::TimestampWithTimeZone 'timestamp with time zone' => Rose::DB::Object::Metadata::Column::TimestampWithTimeZone 'datetime year to fraction' => Rose::DB::Object::Metadata::Column::DatetimeYearToFraction 'datetime year to fraction(1)' => Rose::DB::Object::Metadata::Column::DatetimeYearToFraction1 'datetime year to fraction(2)' => Rose::DB::Object::Metadata::Column::DatetimeYearToFraction2 'datetime year to fraction(3)' => Rose::DB::Object::Metadata::Column::DatetimeYearToFraction3 'datetime year to fraction(4)' => Rose::DB::Object::Metadata::Column::DatetimeYearToFraction4 'datetime year to fraction(5)' => Rose::DB::Object::Metadata::Column::DatetimeYearToFraction5 'timestamp with time zone' => Rose::DB::Object::Metadata::Column::Timestamp 'timestamp without time zone' => Rose::DB::Object::Metadata::Column::Timestamp 'datetime year to second' => Rose::DB::Object::Metadata::Column::DatetimeYearToSecond 'datetime year to minute' => Rose::DB::Object::Metadata::Column::DatetimeYearToMinute 'datetime year to month' => Rose::DB::Object::Metadata::Column::DatetimeYearToMonth 'epoch' => Rose::DB::Object::Metadata::Column::Epoch 'epoch hires' => Rose::DB::Object::Metadata::Column::Epoch::HiRes array => Rose::DB::Object::Metadata::Column::Array set => Rose::DB::Object::Metadata::Column::Set chkpass => Rose::DB::Object::Metadata::Column::Pg::Chkpass =item B Returns the list (in list context) or reference to an array (in scalar context) of registered column type names. =item B Given the string NAME, return the name of the L-derived class L to that name. If a CLASS is passed, then NAME is mapped to CLASS. =item B Get or set the hash that maps names to L-derived class names. This hash is class data. If you want to modify it, I suggest making your own subclass of L and then setting that as the L of your L subclass. If passed MAP (a list of name/class pairs or a reference to a hash of the same) then MAP replaces the current mapping. Returns a list of name/class pairs (in list context) or a reference to the hash of name/class mappings (in scalar context). The default mapping of names to classes is: default => Rose::DB::Object::ConventionManager null => Rose::DB::Object::ConventionManager::Null =item B Get or set a boolean value that indicates whether or not the L-derived L will use L's L method by default (instead of the L method) when L, L, and L objects. The default value is true. =item B Get or set the default value of the L attribute. Defaults to undef. =item B Get or set the default name of the base class used by this metadata class when generating a L classes. The default value is C. See the C L to override this value for a specific metadata object. =item B Returns (or creates, if needed) the single L object associated with CLASS, where CLASS is the name of a L-derived class. =item B This class method should return a reference to a subroutine that maps column names to method names, or false if it does not want to do any custom mapping. The default implementation returns zero (0). If defined, the subroutine should take four arguments: the metadata object, the column name, the column method type, and the method name that would be used if the mapper subroutine did not exist. It should return a method name. =item B Call L on all L, passing PARAMS to each call. PARAMS are name/value pairs. Valid parameters are: =over 4 =item B A L-derived object used to determine which data source the cached metadata will be generated on behalf of. (Each data source has its own set of cached metadata.) This parameter is optional. If it is not passed, then the L-derived object returned by the L method for each L will be used instead. =back =item B Given the relationship type string TYPE, return the name of the L-derived class used to store metadata and create the accessor method(s) for relationships of that type. =item B Get or set the hash that maps relationship type strings to the names of the L-derived classes used to store metadata and create object methods fetch and/or manipulate objects from foreign tables. This hash is class data. If you want to modify it, I suggest making your own subclass of L and then setting that as the L of your L subclass. If passed MAP (a list of type/class pairs or a reference to a hash of the same) then MAP replaces the current relationship type mapping. Returns a list of type/class pairs (in list context) or a reference to the hash of type/class mappings (in scalar context). The default mapping of type names to class names is: 'one to one' => Rose::DB::Object::Metadata::Relationship::OneToOne 'one to many' => Rose::DB::Object::Metadata::Relationship::OneToMany 'many to one' => Rose::DB::Object::Metadata::Relationship::ManyToOne 'many to many' => Rose::DB::Object::Metadata::Relationship::ManyToMany =item B Return a list (in list context) or reference to an array (in scalar context) of the names of all L-derived classes registered under this metadata class's L. =item B Returns the string used to group L-derived class names in the class registry. The default is "Rose::DB::Object::Metadata". =back =head1 CONSTRUCTOR =over 4 =item B Returns (or creates, if needed) the single L associated with a particular L-derived class, modifying or initializing it according to PARAMS, where PARAMS are name/value pairs. Any object method is a valid parameter name, but PARAMS I include a value for the C parameter, since that's how L objects are mapped to their corresponding L-derived class. =back =head1 OBJECT METHODS =over 4 =item B This is an alias for the L method. =item B Add the columns specified by ARGS to the list of columns for the table. Returns the list of columns added in list context, or a reference to an array of columns added in scalar context. Columns can be specified in ARGS in several ways. If an argument is a subclass of L, it is added as-is. If an argument is a plain scalar, it is taken as the name of a scalar column. A column object of the class returned by the method call C<$obj-Ecolumn_type_class('scalar')> is constructed and then added. Otherwise, only name/value pairs are considered, where the name is taken as the column name and the value must be a reference to a hash. If the hash contains the key "primary_key" with a true value, then the column is marked as a L and the column name is added to the list of primary key columns by calling the L method with the column name as its argument. If the hash contains the key "alias", then the value of that key is used as the alias for the column. This is a shorthand equivalent to explicitly calling the L column method. If the hash contains the key "temp" and its value is true, then the column is actually added to the list of L. If the hash contains a key with the same name as a L (e.g., "on_set", "on_load", "inflate") then the value of that key must be a code reference or a reference to an array of code references, which will be L to the list of the column's L for the specified event type. If the hash contains the key "methods", then its value must be a reference to an array or a reference to a hash. The L of the column are then set to the values of the referenced array, or the keys of the referenced hash. The values of the referenced hash are used to set the L for their corresponding method types. If the hash contains the key "add_methods", then its value must be a reference to an array or a reference to a hash. The values of the referenced array or the keys of the referenced hash are added to the column's L. The values of the referenced hash are used to set the L for their corresponding method types. If the "methods" and "add_methods" keys are both set, a fatal error will occur. Then the L method is called with the value of the "type" hash key as its argument (or "scalar" if that key is missing), returning the name of a column class. Finally, a new column object of that class is constructed and is passed all the remaining pairs in the hash reference, along with the name and type of the column. That column object is then added to the list of columns. This is done until there are no more arguments to be processed, or until an argument does not conform to one of the required formats, in which case a fatal error occurs. Example: $meta->add_columns ( # Add a scalar column 'name', # which is roughly equivalent to: # # $class = $meta->column_type_class('scalar'); # $col = $class->new(name => 'name'); # (then add $col to the list of columns) # Add by name/hashref pair with explicit method types age => { type => 'int', default => 5, methods => [ 'get', 'set' ] }, # which is roughly equivalent to: # # $class = $meta->column_type_class('int'); # $col = $class->new(name => 'age', # type => 'int', # default => 5); # $col->auto_method_types('get', 'set'); # (then add $col to the list of columns) # Add by name/hashref pair with additional method type and name size => { type => 'int', add_methods => { 'set' => 'set_my_size' } }, # which is roughly equivalent to: # # $class = $meta->column_type_class('int'); # $col = $class->new(name => 'size', # type => 'int',); # $col->add_auto_method_types('set'); # $col->method_name(set => 'set_my_size'); # (then add $col to the list of columns) # Add a column object directly Rose::DB::Object::Metadata::Column::Date->new( name => 'start_date'), ); =item B This is an alias for the L method. =item B This method behaves like the L method, except that it adds to the list of L. See the documentation for the L method for more information. =item B Add foreign keys as specified by ARGS. Each foreign key must have a L that is unique among all other foreign keys in this L. Foreign keys can be specified in ARGS in several ways. If an argument is a L object (or subclass thereof), it is added as-is. Otherwise, only name/value pairs are considered, where the name is taken as the foreign key name and the value must be a reference to a hash. If the hash contains the key "methods", then its value must be a reference to an array or a reference to a hash. The L of the foreign key are then set to the values of the referenced array, or the keys of the referenced hash. The values of the referenced hash are used to set the L for their corresponding method types. If the hash contains the key "add_methods", then its value must be a reference to an array or a reference to a hash. The values of the referenced array or the keys of the referenced hash are added to the foreign key's L. The values of the referenced hash are used to set the L for their corresponding method types. If the "methods" and "add_methods" keys are both set, a fatal error will occur. A new L object is constructed and is passed all the remaining pairs in the hash reference, along with the name of the foreign key as the value of the "name" parameter. That foreign key object is then added to the list of foreign keys. This is done until there are no more arguments to be processed, or until an argument does not conform to one of the required formats, in which case a fatal error occurs. Example: $meta->add_foreign_keys ( # Add by name/hashref pair with explicit method type category => { class => 'Category', key_columns => { category_id => 'id' }, methods => [ 'get' ], }, # which is roughly equivalent to: # # $fk = Rose::DB::Object::Metadata::ForeignKey->new( # class => 'Category', # key_columns => { category_id => 'id' }, # name => 'category'); # $fk->auto_method_types('get'); # (then add $fk to the list of foreign keys) # Add by name/hashref pair with additional method type and name color => { class => 'Color', key_columns => { color_id => 'id' }, add_methods => { set => 'set_my_color' }, }, # which is roughly equivalent to: # # $fk = Rose::DB::Object::Metadata::ForeignKey->new( # class => 'Color', # key_columns => { color_id => 'id' }, # name => 'color'); # $fk->add_auto_method_types('set'); # $fk->method_name(set => 'set_my_color'); # (then add $fk to the list of foreign keys) # Add a foreign key object directly Rose::DB::Object::Metadata::ForeignKey->new(...), ); For each foreign key added, a corresponding relationship with the same name is added if it does not already exist. The relationship type is determined by the value of the foreign key object's L attribute. The default is "many to one". The class of the relationship is chosen by calling L with the relationship type as an argument. =item B This method is an alias for L. =item B Add COLUMNS to the list of columns that make up the primary key. COLUMNS can be a list or reference to an array of column names. =item B This is an alias for the L method. =item B Add relationships as specified by ARGS. Each relationship must have a L that is unique among all other relationships in this L. Relationships can be specified in ARGS in several ways. If an argument is a subclass of L, it is added as-is. Otherwise, only name/value pairs are considered, where the name is taken as the relationship name and the value must be a reference to a hash. If the hash contains the key "methods", then its value must be a reference to an array or a reference to a hash. The L of the relationship are then set to the values of the referenced array, or the keys of the referenced hash. The values of the referenced hash are used to set the L for their corresponding method types. If the hash contains the key "add_methods", then its value must be a reference to an array or a reference to a hash. The values of the referenced array or the keys of the referenced hash are added to the relationship's L. The values of the referenced hash are used to set the L for their corresponding method types. If the "methods" and "add_methods" keys are both set, a fatal error will occur. Then the L method is called with the value of the C hash key as its argument, returning the name of a relationship class. Finally, a new relationship object of that class is constructed and is passed all the remaining pairs in the hash reference, along with the name and type of the relationship. That relationship object is then added to the list of relationships. This is done until there are no more arguments to be processed, or until an argument does not conform to one of the required formats, in which case a fatal error occurs. Example: $meta->add_relationships ( # Add by name/hashref pair with explicit method type category => { type => 'many to one', class => 'Category', column_map => { category_id => 'id' }, methods => [ 'get' ], }, # which is roughly equivalent to: # # $class = $meta->relationship_type_class('many to one'); # $rel = $class->new(class => 'Category', # column_map => { category_id => 'id' }, # name => 'category'); # $rel->auto_method_types('get'); # (then add $rel to the list of relationships) # Add by name/hashref pair with additional method type and name color => { type => 'many to one', class => 'Color', column_map => { color_id => 'id' }, add_methods => { set => 'set_my_color' }, }, # which is roughly equivalent to: # # $class = $meta->relationship_type_class('many to one'); # $rel = $class->new(class => 'Color', # column_map => { color_id => 'id' }, # name => 'color'); # $rel->add_auto_method_types('set'); # $fk->method_name(set => 'set_my_color'); # (rel add $fk to the list of foreign keys) # Add a relationship object directly Rose::DB::Object::Metadata::Relationship::OneToOne->new(...), ); =item B This method is an alias for L. =item B Add new unique keys specified by KEYS. Unique keys can be specified in KEYS in two ways. If an argument is a L object (or subclass thereof), then its L is set to the metadata object itself, and it is added. Otherwise, an argument must be a single column name or a reference to an array of column names that make up a unique key. A new L is created, with its L set to the metadata object itself, and then the unique key object is added to this list of unique keys for this L. =item B Set the L for the column named NAME to ALIAS. It is sometimes necessary to use an alias for a column because the column name conflicts with an existing L method name. For example, imagine a column named "save". The L API already defines a method named L, so obviously that name can't be used for the accessor method for the "save" column. To solve this, make an alias: $meta->alias_column(save => 'save_flag'); See the L documentation or call the L method to determine if a method name is reserved. =item B Get or set the boolean flag that indicates whether or not the associated L-derived class should try to inline column values that L does not handle correctly when they are bound to placeholders using L. The default value is false. Enabling this flag reduces the performance of the L and L operations on the L-derived object. But it is sometimes necessary to enable the flag because some L drivers do not (or cannot) always do the right thing when binding values to placeholders in SQL statements. For example, consider the following SQL for the Informix database: CREATE TABLE test (d DATETIME YEAR TO SECOND); INSERT INTO test (d) VALUES (CURRENT); This is valid Informix SQL and will insert a row with the current date and time into the "test" table. Now consider the following attempt to do the same thing using L placeholders (assume the table was already created as per the CREATE TABLE statement above): $sth = $dbh->prepare('INSERT INTO test (d) VALUES (?)'); $sth->execute('CURRENT'); # Error! What you'll end up with is an error like this: DBD::Informix::st execute failed: SQL: -1262: Non-numeric character in datetime or interval. In other words, L has tried to quote the string "CURRENT", which has special meaning to Informix only when it is not quoted. In order to make this work, the value "CURRENT" must be "inlined" rather than bound to a placeholder when it is the value of a "DATETIME YEAR TO SECOND" column in an Informix database. =item B Get or set a flag that indicates whether or not classes related to this L through a L or other L will be automatically loaded when this L is Ld. The default value is true. =item B This method is only applicable if this metadata object is associated with a L-derived class. It simply calls the class method of the same name that belongs to the L-derived L associated with this metadata object. =item B Get or set the database catalog for this L. This setting will B any L in the L object. Use this method only if you know that the L will always point to a specific catalog, regardless of what the L-derived database handle object specifies. =item B Get or set the L-derived class associated with this metadata object. This is the class where the accessor methods for each column will be created (by L). =item B Returns the name of the L-derived class associated with the C, C, and C
specified by the name/value paris in PARAMS. Catalog and/or schema maybe omitted if unknown or inapplicable, and the "best" match will be returned. Returns undef if there is no class name registered under the specified PARAMS. Note: This method may also be called as a class method, but may require explicit C and/or C arguments when dealing with databases that support these concepts I have default implicit values for them. =item B This method is only applicable if this metadata object is associated with a L-derived class. It simply calls the class method of the same name that belongs to the L-derived L associated with this metadata object. =item B Get or set the column named NAME. If just NAME is passed, the L-derived column object for the column of that name is returned. If no such column exists, undef is returned. If both NAME and COLUMN are passed, then COLUMN must be a L-derived object. COLUMN has its L set to NAME, and is then stored as the column metadata object for NAME, replacing any existing column. If both NAME and HASHREF are passed, then the combination of NAME and HASHREF must form a name/value pair suitable for passing to the L method. The new column specified by NAME and HASHREF replaces any existing column. =item B Get or set the full list of columns. If ARGS are passed, the column list is cleared and then ARGS are passed to the L method. Returns a list of column objects in list context, or a reference to an array of column objects in scalar context. =item B Returns the name of the "get" method for the column named NAME. This is just a shortcut for C<$meta-Ecolumn(NAME)-Eaccessor_method_name>. =item B Returns a list (in list context) or a reference to the array (in scalar context) of the names of the "set" methods for all the columns, in the order that the columns are returned by L. =item B Get or set the hash that maps column names to their aliases. If passed MAP (a list of name/value pairs or a reference to a hash) then MAP replaces the current alias mapping. Returns a reference to the hash that maps column names to their aliases. Note that modifying this map has no effect if L, L, or L has already been called for the current L. =item B Returns the name of the "set" method for the column named NAME. This is just a shortcut for C<$meta-Ecolumn(NAME)-Emutator_method_name>. =item B Returns a list (in list context) or a reference to the array (in scalar context) of the names of the "set" methods for all the columns, in the order that the columns are returned by L. =item B Returns a list (in list context) or a reference to an array (in scalar context) of column names. =item B Get or set the code reference to the subroutine used to map column names to method names. If undefined, then the L class method is called in order to initialize it. If still undefined or false, then the "default" method name is used. If defined, the subroutine should take four arguments: the metadata object, the column name, the column method type, and the method name that would be used if the mapper subroutine did not exist. It should return a method name. =item B Returns the name of the "get_set" method for the column named NAME. This is just a shortcut for C<$meta-Ecolumn(NAME)-Erw_method_name>. =item B Returns a list (in list context) or a reference to the array (in scalar context) of the names of the "get_set" methods for all the columns, in the order that the columns are returned by L. =item B Get or set a boolean value that influences the default value of the L attribute for each L in this L. See the documentation for L's L attribute for more information. Defaults to the value returned by the L class method. =item B Get or set the convention manager for this L. Defaults to the return value of the L method. If undef is passed, then a L object is stored instead. If a L-derived object is passed, its L attribute set to this metadata object and then it is used as the convention manager for this L. If a L-derived class name is passed, a new object of that class is created with its L attribute set to this metadata object. Then it is used as the convention manager for this L. If a convention manager name is passed, then the corresponding class is looked up in the L, a new object of that class is constructed, its L attribute set to this metadata object, and it is used as the convention manager for this L. If there is no class mapped to NAME, a fatal error will occur. See the L documentation for more information on convention managers. =item B Returns the L-derived object associated with this metadata object's L. A fatal error will occur if L is undefined or if the L object could not be created. =item B Get or set a boolean value that indicates whether or not the L associated with this metadata object will L related objects when the parent object is L. See the documentation for L's L method for details. The default value is false. =item B Get or set a boolean value that indicates whether or not the L associated with this metadata object will L speculatively by default. See the documentation for L's L method for details. The default value is false. =item B Get or set a boolean value that indicates whether or not the L associated with this metadata object will L only an object's modified columns by default (instead of updating all columns). See the documentation for L's L method for details. The default value is false. =item B Delete the column named NAME. =item B Delete all of the L. =item B Delete the type/class L entry for the column type TYPE. =item B Delete the name/class L entry for the convention manager class mapped to NAME. =item B Delete the L named NAME. =item B Delete all of the L. =item B Delete the relationship named NAME. =item B Delete all of the relationships. =item B Delete the type/class mapping entry for the relationship type TYPE. =item B Delete all of the unique key definitions. =item B Get or set the error mode of the L that fronts the table described by this L object. If the error mode is false, then it defaults to the return value of the C method, which is "fatal" by default. The error mode determines what happens when a L method encounters an error. The "return" error mode causes the methods to behave as described in the L documentation. All other error modes cause an action to be performed before (possibly) returning as per the documentation (depending on whether or not the "action" is some variation on "throw an exception.") Valid values of MODE are: =over 4 =item carp Call L with the value of the object L as an argument. =item cluck Call L with the value of the object L as an argument. =item confess Call L with the value of the object L as an argument. =item croak Call L with the value of the object L as an argument. =item fatal An alias for the "croak" mode. =item return Return a value that indicates that an error has occurred, as described in the L for each method. =back In all cases, the object's L attribute will also contain the error message. =item B Returns the first column, determined by the order that columns were L, or undef if there are no columns. =item B Get or set the foreign key named NAME. NAME should be the name of the thing being referenced by the foreign key, I the name of any of the columns that make up the foreign key. If called with just a NAME argument, the foreign key stored under that name is returned. Undef is returned if there is no such foreign key. If both NAME and FOREIGNKEY are passed, then FOREIGNKEY must be a L-derived object. FOREIGNKEY has its L set to NAME, and is then stored, replacing any existing foreign key with the same name. If both NAME and HASHREF are passed, then the combination of NAME and HASHREF must form a name/value pair suitable for passing to the L method. The new foreign key specified by NAME and HASHREF replaces any existing foreign key with the same name. =item B Get or set the full list of foreign keys. If ARGS are passed, the foreign key list is cleared and then ARGS are passed to the L method. Returns a list of foreign key objects in list context, or a reference to an array of foreign key objects in scalar context. =item B This method is the same as L except that it only returns the generated value for the first primary key column, rather than the entire list of values. Use this method only when there is a single primary key column (or not at all). =item B Given the L-derived object DB, generate and return a list of new primary key column values for the table described by this metadata object. If a L is defined, it will be called (passed this metadata object and the DB) and its value returned. If no L is defined, new primary key values will be generated, if possible, using the native facilities of the current database. Note that this may not be possible for databases that auto-generate such values only after an insertion. In that case, undef will be returned. =item B Get or set a boolean value that indicates whether or not the L method will create L for unique indexes that have predicates. The default value is false. This feature is currently only supported for PostgreSQL. Here's an example of a unique index that has a predicate: CREATE UNIQUE INDEX my_idx ON mytable (mycolumn) WHERE mycolumn > 123; The predicate in this case is C 123>. Predicated unique indexes differ semantically from unpredicated unique indexes in that predicates generally cause the index to only apply to part of a table. L expects L to uniquely identify a row within a table. Predicated indexes that fail to do so due to their predicates should therefore not have L objects created for them, thus the false default for this attribute. =item B Returns the default L-derived object used as the L for this L. This object will be of the class returned by L. Override this method in your L subclass, or L the "default" convention manager class, in order to use a different convention manager class. See the L section of the L documentation for an example of the subclassing approach. =item B Initialize the L-derived class associated with this metadata object by creating accessor methods for each column and foreign key. The L name and the L must be defined or a fatal error will occur. If any column name in the primary key or any of the unique keys does not exist in the list of L, then that primary or unique key is deleted. (As per the above, this will trigger a fatal error if any column in the primary key is not in the column list.) ARGS, if any, are passed to the call to L that actually creates the methods. If L is true, then the L method will be called at the end of the initialization process. =item B Get or set a boolean value that indicates whether or not this L was Ld. A successful call to the L method will automatically set this flag to true. =item B This method creates a L-derived class to manage objects of this L. To do so, it simply calls L, passing all arguments, and then Luates the result. See the L documentation for more information. =item B Create object methods in L for each L, L, and L. This is done by calling L, L, L, and L, in that order. ARGS are name/value pairs which are passed on to the other C calls. They are all optional. Valid ARGS are: =over 4 =item * C If set to a true value, a method will not be created if there is already an existing method with the same named. =item * C If set to a true value, override any existing method with the same name. =back In the absence of one of these parameters, any method name that conflicts with an existing method name will cause a fatal error. =item B Create accessor/mutator methods in L for each L. ARGS are name/value pairs, and are all optional. Valid ARGS are: =over 4 =item * C If set to a true value, a method will not be created if there is already an existing method with the same named. =item * C If set to a true value, override any existing method with the same name. =back For each L in each column, the method name is determined by passing the column name and the method type to L. If the resulting method name is reserved (according to L, a fatal error will occur. The object methods for each column are created by calling the column object's L method. =item B Create object methods in L for each L. ARGS are name/value pairs, and are all optional. Valid ARGS are: =over 4 =item * C If set to a true value, a method will not be created if there is already an existing method with the same named. =item * C If set to a true value, override any existing method with the same name. =back For each L in each foreign key, the method name is determined by passing the method type to the L method of the foreign key object, or the L method if the L call returns a false value. If the method name is reserved (according to L), a fatal error will occur. The object methods for each foreign key are created by calling the foreign key object's L method. Foreign keys and relationships with the L "one to one" or "many to one" both encapsulate essentially the same information. They are kept in sync when this method is called by setting the L attribute of each "L" or "L" relationship object to be the corresponding foreign key object. =item B This method behaves like the L method, except that it works with L. See the documentation for the L method for more information on non-persistent columns. =item B Create object methods in L for each L. ARGS are name/value pairs, and are all optional. Valid ARGS are: =over 4 =item * C If set to a true value, a method will not be created if there is already an existing method with the same named. =item * C If set to a true value, override any existing method with the same name. =back For each L in each relationship, the method name is determined by passing the method type to the L method of the relationship object, or the L method if the L call returns a false value. If the method name is reserved (according to L), a fatal error will occur. The object methods for each relationship are created by calling the relationship object's L method. Foreign keys and relationships with the L "one to one" or "many to one" both encapsulate essentially the same information. They are kept in sync when this method is called by setting the L attribute of each "L" or "L" relationship object to be the corresponding foreign key object. If a relationship corresponds exactly to a foreign key, and that foreign key already made an object method, then the relationship is not asked to make its own method. =item B Get or set the default name of the base class used by this specific metadata object when generating a L class, using either the L or L methods. The default value is determined by the C L. =item B Returns the name of the column manipulated by the method named METHOD. =item B Looks up the column named NAME and calls L with the column and TYPE as argument. If no such column exists, a fatal error will occur. =item B Given a L-derived column object and a column L name, return the corresponding method name that should be used for it. Several entities are given an opportunity to determine the name. They are consulted in the following order. =over 4 =item 1. If a custom-defined L exists, then it is used to generate the method name and this name is returned. =item 2. If a method name has been L, for this type in the column object itself, then this name is returned. =item 3. If the L's L method returns a defined value, then this name is returned. =item 4. Otherwise, the default naming rules as defined in the column class itself are used. =back =item B Given the method name NAME and the class name CLASS, returns true if the method name is reserved (i.e., is used by the CLASS API), false otherwise. =item B This method behaves like the L method, except that it works with L. See the documentation for the L method for more information on non-persistent columns. =item B Get or set the full list of non-persistent columns. If ARGS are passed, the non-persistent column list is cleared and then ARGS are passed to the L method. Returns a list of non-persistent column objects in list context, or a reference to an array of non-persistent column objects in scalar context. Non-persistent columns allow the creation of object attributes and associated accessor/mutator methods exactly like those associated with L, but I ever sending any of these attributes to (or pulling any these attributes from) the database. Non-persistent columns are tracked entirely separately from L. L, L, and listing non-persistent columns has no affect on the list of normal (i.e., "persistent") L. You cannot query the database (e.g., using L) and filter on a non-persistent column; non-persistent columns do not exist in the database. This feature exists solely to leverage the method creation abilities of the various column classes. =item B Returns the name of the "get" method for the L column named NAME. This is just a shortcut for C<$meta-Enonpersistent_column(NAME)-Eaccessor_method_name>. =item B Returns a list (in list context) or a reference to the array (in scalar context) of the names of the "set" methods for all the L columns, in the order that the columns are returned by L. =item B Returns the name of the "set" method for the L column named NAME. This is just a shortcut for C<$meta-Enonpersistent_column(NAME)-Emutator_method_name>. =item B Returns a list (in list context) or a reference to the array (in scalar context) of the names of the "set" methods for all the L, in the order that the columns are returned by L. =item B Returns a list (in list context) or a reference to an array (in scalar context) of L column names. =item B This is an alias for the L method. =item B Get or set a reference to a subroutine or a reference to an array of code references that will be called just after the L method runs. Each referenced subroutine will be passed the metadata object itself and any arguments passed to the call to L. =item B Get or set a reference to a subroutine or a reference to an array of code references that will be called just before the L method runs. Each referenced subroutine will be passed the metadata object itself and any arguments passed to the call to L. =item B Get or set the L object that stores the list of column names that make up the primary key for this table. =item B Get or set the list of columns that make up the primary key. COLUMNS should be a list of column names or L-derived objects. Returns all of the columns that make up the primary key. Each column is a L-derived column object if a L object with the same name exists, or just the column name otherwise. In scalar context, a reference to an array of columns is returned. In list context, a list is returned. This method is just a shortcut for the code: $meta->primary_key->columns(...); See the L method and the L class for more information. =item B Get or set the names of the columns that make up the table's primary key. NAMES should be a list or reference to an array of column names. Returns the list of column names (in list context) or a reference to the array of column names (in scalar context). This method is just a shortcut for the code: $meta->primary_key->column_names(...); See the L method and the L class for more information. =item B Get or set the subroutine used to generate new primary key values for the primary key columns of this table. The subroutine will be passed two arguments: the current metadata object and the L-derived object that points to the current database. The subroutine is expected to return a list of values, one for each primary key column. The values must be in the same order as the corresponding columns returned by L. (i.e., the first value belongs to the first column returned by L, the second value belongs to the second column, and so on.) =item B Get or set the list of database sequence names used to populate the primary key columns. The sequence names must be in the same order as the L. NAMES may be a list or reference to an array of sequence names. Returns a list (in list context) or reference to the array (in scalar context) of sequence names. If you do not set this value, it will be derived for you based on the name of the primary key columns. In the common case, you do not need to be concerned about this method. If you are using the built-in SERIAL or AUTO_INCREMENT types in your database for your primary key columns, everything should just work. =item B By default, secondary metadata derived from the attributes of this object is created and cached on demand. Call this method to pre-cache this metadata all at once. This method is useful when running in an environment like L where it's advantageous to load as much data as possible on start-up. PARAMS are name/value pairs. Valid parameters are: =over 4 =item B A L-derived object used to determine which data source the cached metadata will be generated on behalf of. (Each data source has its own set of cached metadata.) This parameter is optional. If it is not passed, then the L-derived object returned by the L method for this L will be used instead. =back =item B Get or set the relationship named NAME. If just NAME is passed, the L-derived relationship object for that NAME is returned. If no such relationship exists, undef is returned. If both NAME and RELATIONSHIP are passed, then RELATIONSHIP must be a L-derived object. RELATIONSHIP has its L set to NAME, and is then stored as the relationship metadata object for NAME, replacing any existing relationship. If both NAME and HASHREF are passed, then the combination of NAME and HASHREF must form a name/value pair suitable for passing to the L method. The new relationship specified by NAME and HASHREF replaces any existing relationship. =item B Get or set the full list of relationships. If ARGS are passed, the relationship list is cleared and then ARGS are passed to the L method. Returns a list of relationship objects in list context, or a reference to an array of relationship objects in scalar context. =item B Replace the column named NAME with a newly constructed column. This method is equivalent to L any existing column named NAME and then L a new one. In other words, this: $meta->replace_column($name => $value); is equivalent to this: $meta->delete_column($name); $meta->add_column($name => $value); The value of the new column may be a L-derived object or a reference to a hash suitable for passing to the L method. =item B Get or set the database schema for this L. This setting will B any L in the L object. Use this method only if you know that the L will always point to a specific schema, regardless of what the L-derived database handle object specifies. =item B Set up all the metadata for this L in a single method call. This method is a convenient shortcut. It does its work by delegating to other methods. The L method does nothing if the metadata object is already initialized (according to the L method). PARAMS are method/arguments pairs. In general, the following transformations apply. Given a method/arrayref pair: METHOD => [ ARG1, ARG2 ] The arguments will be removed from their array reference and passed to METHOD like this: $meta->METHOD(ARG1, ARG2); Given a method/value pair: METHOD => ARG The argument will be passed to METHOD as-is: $meta->METHOD(ARG); There are two exceptions to these transformation rules. If METHOD is "L" or "L" and the argument is a reference to an array containing only non-reference values, then the array reference itself is passed to the method. For example, this pair: unique_key => [ 'name', 'status' ] will result in this method call: $meta->unique_key([ 'name', 'status' ]); (Note that these method names are I. This exception does I apply to the I variants, "L" and "L".) If METHOD is "helpers", then the argument is dereferenced (if it's an array reference) and passed on to L. That is, this: helpers => [ 'load_or_save', { load_or_insert => 'find_or_create' } ], Is equivalent to having this in your L: use Rose::DB::Object::Helpers 'load_or_save', { load_or_insert => 'find_or_create' }; Method names may appear more than once in PARAMS. The methods are called in the order that they appear in PARAMS, with the exception of the L (or L) method, which is always called last. If "initialize" is not one of the method names, then it will be called automatically (with no arguments) at the end. If you do not want to pass any arguments to the L method, standard practice is to omit it. If "auto_initialize" is one of the method names, then the L method will be called instead of the L method. This is useful if you want to manually set up a few pieces of metadata, but want the auto-initialization system to set up the rest. The name "auto" is considered equivalent to "auto_initialize", but any arguments are ignored unless they are encapsulated in a reference to an array. For example, these are equivalent: $meta->setup( table => 'mytable', # Call auto_initialize() with no arguments auto_initialize => [], ); # This is another way of writing the same thing as the above $meta->setup( table => 'mytable', # The value "1" is ignored because it's not an arrayref, # so auto_initialize() will be called with no arguments. auto => 1, ); Finally, here's a full example of a L method call followed by the equivalent "long-hand" implementation. $meta->setup ( table => 'colors', columns => [ code => { type => 'character', length => 3, not_null => 1 }, name => { type => 'varchar', length => 255 }, ], primary_key_columns => [ 'code' ], unique_key => [ 'name' ], ); The L method call above is equivalent to the following code: unless($meta->is_initialized) { $meta->table('colors'); $meta->columns( [ code => { type => 'character', length => 3, not_null => 1 }, name => { type => 'varchar', length => 255 }, ]); $meta->primary_key_columns('code'); $meta->unique_key([ 'name' ]), $meta->initialize; } =item B Get or set a boolean value that indicates whether or not to prefix the columns with the table name in the SQL used to L an object. The default value is false. For example, here is some SQL that might be used to L an object, as generated with L set to false: SELECT id, name FROM dogs WHERE id = 5; Now here's how it would look with L set to true: SELECT dogs.id, dogs.name FROM dogs WHERE dogs.id = 5; =item B
Get or set the name of the database table. The table name should not include any sort of prefix to indicate the L or L. =item B This method is an alias for L. =item B Get or set the list of unique keys for this table. If KEYS is passed, any existing keys will be deleted and KEYS will be passed to the L method. Returns the list (in list context) or reference to an array (in scalar context) of L objects. =item B Return the unique key L NAME, or undef if no such key exists. =item B Returns a list (in list context) or a reference to an array (in scalar context) or references to arrays of the column names that make up each unique key. That is: # Example of a scalar context return value [ [ 'id', 'name' ], [ 'code' ] ] # Example of a list context return value ([ 'id', 'name' ], [ 'code' ]) =back =head1 AUTO-INITIALIZATION METHODS These methods are associated with the L process. Calling any of them will cause the auto-initialization code to be loaded, which costs memory. This should be considered an implementation detail for now. Regardless of the implementation details, you should still avoid calling any of these methods unless you plan to do some auto-initialization. No matter how generic they may seem (e.g., L), rest assured that none of these methods are remotely useful I you are doing auto-initialization. =head2 CLASS METHODS =over 4 =item B Get or set the default brace style used in the Perl code generated by the perl_* object methods. STYLE must be either "k&r" or "bsd". The default value is "k&r". =item B Get or set the default integer number of spaces used for each level of indenting in the Perl code generated by the perl_* object methods. The default value is 4. =item B Get or set the default style of the unique key initialization used in the Perl code generated by the L method. STYLE must be "array" or "object". The default value is "array". See the L method for examples of the two styles. =back =head2 OBJECT METHODS =over 4 =item B Auto-generate L-derived objects for each column in the table. Note that this method does not modify the metadata object's list of L. It simply returns a list of column objects. Calling this method in void context will cause a fatal error. Returns a list of column objects (in list context) or a reference to a hash of column objects, keyed by column name (in scalar context). The hash reference return value is intended to allow easy modification of the auto-generated column objects. Example: $columns = $meta->auto_generate_columns; # hash ref return value # Make some changes $columns->{'name'}->length(10); # set different length $columns->{'age'}->default(5); # set different default ... # Finally, set the column list $meta->columns(values %$columns); If you do not want to modify the auto-generated columns, you should use the L method instead. A fatal error will occur unless at least one column was auto-generated. =item B Auto-generate L objects for each foreign key in the table. Note that this method does not modify the metadata object's list of L. It simply returns a list of foreign key objects. Calling this method in void context will cause a fatal error. A warning will be issued if a foreign key could not be generated because no L-derived class was found for the foreign table. PARAMS are optional name/value pairs. If a C parameter is passed with a true value, then the warning described above will not be issued. Returns a list of foreign key objects (in list context) or a reference to an array of foreign key objects (in scalar context). If you do not want to inspect or modify the auto-generated foreign keys, but just want them to populate the metadata object's L list, you should use the L method instead. B This method works with MySQL only when using the InnoDB storage type. =item B Auto-generate L objects for each unique key in the table. Note that this method does not modify the metadata object's list of L. It simply returns a list of unique key objects. Calling this method in void context will cause a fatal error. Returns a list of unique key objects (in list context) or a reference to an array of unique key objects (in scalar context). If you do not want to inspect or modify the auto-generated unique keys, but just want them to populate the metadata object's L list, you should use the L method instead. =item B Returns a list (in list context) or a reference to an array (in scalar context) of the names of the columns that make up the primary key for this table. Note that this method does not modify the metadata object's L. It simply returns a list of column names. Calling this method in void context will cause a fatal error. This method is rarely called explicitly. Usually, you will use the L method instead. A fatal error will occur unless at least one column name can be retrieved. (This method uses the word "retrieve" instead of "generate" like its sibling methods above because it does not generate objects; it simply returns column names.) =item B Auto-initialize the entire metadata object. This is a wrapper for the individual "auto_init_*" methods, and is roughly equivalent to this: $meta->auto_init_columns(...); $meta->auto_init_primary_key_columns; $meta->auto_init_unique_keys(...); $meta->auto_init_foreign_keys(...); $meta->auto_init_relationships(...); $meta->initialize; PARAMS are optional name/value pairs. When applicable, these parameters are passed on to each of the "auto_init_*" methods. Valid parameters are: =over 4 =item B By default, if a class is a L (according to the L method of the L), then relationships directly between that class and the current L will not be created. Set this parameter to true to allow such relationships to be created. B If some classes that are not actually map classes are being skipped, you should not use this parameter to force them to be included. It's more appropriate to make your own custom L subclass and then override the L method to make the correct determination. =item B If true, then the auto-generated columns, unique keys, foreign keys, and relationships entirely replace any existing columns, unique keys, foreign keys, and relationships, respectively. =item B If true, then any database connections retained by the metadata objects belonging to the various L-derived classes participating in the auto-initialization process will remain connected until an explicit call to the L class method. =item B A boolean value indicating whether or not foreign key metadata will be auto-initialized. Defaults to true. =item B A boolean value or a reference to an array of relationship L names. If set to a simple boolean value, then the all types of relationships will be considered for auto-initialization. If set to a list of relationship type names, then only relationships of those types will be considered. Defaults to true. =item B A boolean value indicating whether or not unique key metadata will be auto-initialized. Defaults to true. =back During initialization, if one of the columns has a method name that clashes with a L, then the L will be called to remedy the situation by aliasing the column. If the name still conflicts, then a fatal error will occur. A fatal error will occur if auto-initialization fails. =item B Auto-generate L objects for this table, then populate the list of L. PARAMS are optional name/value pairs. If a C parameter is passed with a true value, then the auto-generated columns replace any existing columns. Otherwise, any existing columns are left as-is. =item B Auto-generate L objects for this table, then populate the list of L. PARAMS are optional name/value pairs. If a C parameter is passed with a true value, then the auto-generated foreign keys replace any existing foreign keys. Otherwise, any existing foreign keys are left as-is. B This method works with MySQL only when using the InnoDB storage type. =item B Auto-retrieve the names of the columns that make up the primary key for this table, then populate the list of L. A fatal error will occur unless at least one primary key column name could be retrieved. =item B Auto-populate the list of L for this L. PARAMS are optional name/value pairs. =over 4 =item B By default, if a class is a L (according to the L method of the L), then relationships directly between that class and the current L will not be created. Set this parameter to true to allow such relationships to be created. B If some classes that are not actually map classes are being skipped, you should not use this parameter to force them to be included. It's more appropriate to make your own custom L subclass and then override the L method to make the correct determination. =item B If true, then the auto-generated relationships replace any existing relationships. Otherwise, any existing relationships are left as-is. =item B A reference to an array of relationship L names. Only relationships of these types will be created. If omitted, relationships of L will be created. If passed a reference to an empty array, no relationships will be created. =item B This is an alias for the C parameter. =item B This is the same as the C parameter except that it also accepts a boolean value. If true, then relationships of L will be created. If false, then none will be created. =back Assume that this L is called C and any hypothetical foreign class is called C. Relationships are auto-generated according to the following rules. =over 4 =item * A L relationship is created between C and C if C has a foreign key that points to C. This is not done, however, if C has a L relationship pointing to C that references the same columns as the foreign key in C that points to C, or if C is a map class (according to the L's L method). The relationship name is generated by the L's L method. =item * A L relationship is created between C and C if there exists a L (according to the convention manager's L method) with exactly two foreign keys, one pointing to L and on pointing to C. The relationship name is generated by creating a L version of the name of the foreign key in the map class that points to C. =back In all cases, if there is an existing, semantically identical relationship, then a new relationship is not auto-generated. Similarly, any existing methods with the same names are not overridden by methods associated with auto-generated relationships. =item B Auto-generate L objects for this table, then populate the list of L. PARAMS are name/value pairs. If a C parameter is passed with a true value, then the auto-generated unique keys replace any existing unique keys. Otherwise, any existing unique keys are left as-is. =item B Get or set the code reference to the subroutine used to alias columns have, or would generate, one or more method names that clash with L. The subroutine should take two arguments: the metadata object and the column name. The C<$_> variable will also be set to the column name at the time of the call. The subroutine should return an L for the column. The default column alias generator simply appends the string "_col" to the end of the column name and returns that as the alias. =item B Get or set the code reference to the subroutine used to generate L names. B This code will only be called if the L's L method fails to (or declines to) produce a defined foreign key name. The subroutine should take two arguments: a metadata object and a L object. It should return a name for the foreign key. Each foreign key must have a name that is unique within the class. By default, this name will also be the name of the method generated to access the object referred to by the foreign key, so it must be unique among method names in the class as well. The default foreign key name generator uses the following algorithm: If the foreign key has only one column, and if the name of that column ends with an underscore and the name of the referenced column, then that part of the column name is removed and the remaining string is used as the foreign key name. For example, given the following tables: CREATE TABLE categories ( id SERIAL PRIMARY KEY, ... ); CREATE TABLE products ( category_id INT REFERENCES categories (id), ... ); The foreign key name would be "category", which is the name of the referring column ("category_id") with an underscore and the name of the referenced column ("_id") removed from the end of it. If the foreign key has only one column, but it does not meet the criteria described above, then "_object" is appended to the name of the referring column and the resulting string is used as the foreign key name. If the foreign key has more than one column, then the foreign key name is generated by replacing double colons and case-transitions in the referenced class name with underscores, and then converting to lowercase. For example, if the referenced table is fronted by the class My::TableOfStuff, then the generated foreign key name would be "my_table_of_stuff". In all of the scenarios above, if the generated foreign key name is still not unique within the class, then a number is appended to the end of the name. That number is incremented until the name is unique. In practice, rather than setting a custom foreign key name generator, it's usually easier to simply set the foreign key name(s) manually after auto-initializing the foreign keys (but I calling L or L, of course). =item B Auto-initialize the columns, primary key, foreign keys, and unique keys, then return the Perl source code for a complete L-derived class definition. PARAMS are optional name/value pairs that may include the following: =over 4 =item B The brace style to use in the generated Perl code. STYLE must be either "k&r" or "bsd". The default value is determined by the return value of the L class method. =item B The integer number of spaces to use for each level of indenting in the generated Perl code. The default value is determined by the return value of the L class method. =item B The list of base classes to use in the generated class definition. CLASSES should be a single class name, or a reference to an array of class names. The default base class is L. =item B If true, then the generated class definition will include a call to the L method. Otherwise, the generated code will contain individual methods calls. The default value for this parameter is B; the L method is the recommended way to initialize a class. =back This method is simply a wrapper (with some glue) for the following methods: L, L, L, L, and L. The "braces" and "indent" parameters are passed on to these other methods. Here's a complete example, which also serves as an example of the individual "perl_*" methods that this method wraps. First, the table definitions. CREATE TABLE topics ( id SERIAL PRIMARY KEY, name VARCHAR(32) ); CREATE TABLE codes ( k1 INT NOT NULL, k2 INT NOT NULL, k3 INT NOT NULL, name VARCHAR(32), PRIMARY KEY(k1, k2, k3) ); CREATE TABLE products ( id SERIAL PRIMARY KEY, name VARCHAR(32) NOT NULL, flag BOOLEAN NOT NULL DEFAULT 't', status VARCHAR(32) DEFAULT 'active', topic_id INT REFERENCES topics (id), fk1 INT, fk2 INT, fk3 INT, last_modified TIMESTAMP, date_created TIMESTAMP, FOREIGN KEY (fk1, fk2, fk3) REFERENCES codes (k1, k2, k3) ); CREATE TABLE prices ( id SERIAL PRIMARY KEY, product_id INT REFERENCES products (id), price DECIMAL(10,2) NOT NULL DEFAULT 0.00, region CHAR(2) NOT NULL DEFAULT 'US' ); First we'll auto-initialize the classes. package Code; use base qw(Rose::DB::Object); __PACKAGE__->meta->auto_initialize; package Category; use base qw(Rose::DB::Object); # Explicit table name required because the class name # does not match up with the table name in this case. __PACKAGE__->meta->table('topics'); __PACKAGE__->meta->auto_initialize; package Product; use base qw(Rose::DB::Object); __PACKAGE__->meta->auto_initialize; package Price; use base qw(Rose::DB::Object); __PACKAGE__->meta->auto_initialize; Now we'll print the C class definition; print Product->meta->perl_class_definition(braces => 'bsd', indent => 2); The output looks like this: package Product; use strict; use base qw(Rose::DB::Object); __PACKAGE__->meta->setup ( table => 'products', columns => [ id => { type => 'integer', not_null => 1 }, name => { type => 'varchar', length => 32, not_null => 1 }, flag => { type => 'boolean', default => 'true', not_null => 1 }, status => { type => 'varchar', default => 'active', length => 32 }, topic_id => { type => 'integer' }, fk1 => { type => 'integer' }, fk2 => { type => 'integer' }, fk3 => { type => 'integer' }, last_modified => { type => 'timestamp' }, date_created => { type => 'timestamp' }, ], primary_key_columns => [ 'id' ], foreign_keys => [ code => { class => 'Code', key_columns => { fk1 => 'k1', fk2 => 'k2', fk3 => 'k3', }, }, topic => { class => 'Category', key_columns => { topic_id => 'id', }, }, ], relationships => [ prices => { class => 'Price', key_columns => { id => 'product_id' }, type => 'one to many', }, ], ); 1; Here's the output when the C parameter is explicitly set to false. print Product->meta->perl_class_definition(braces => 'bsd', indent => 2, use_setup => 0); Note that this approach is not recommended, but exists for historical reasons. package Product; use strict; use base qw(Rose::DB::Object); __PACKAGE__->meta->table('products'); __PACKAGE__->meta->columns ( id => { type => 'integer', not_null => 1 }, name => { type => 'varchar', length => 32, not_null => 1 }, flag => { type => 'boolean', default => 'true', not_null => 1 }, status => { type => 'varchar', default => 'active', length => 32 }, topic_id => { type => 'integer' }, fk1 => { type => 'integer' }, fk2 => { type => 'integer' }, fk3 => { type => 'integer' }, last_modified => { type => 'timestamp' }, date_created => { type => 'timestamp' }, ); __PACKAGE__->meta->primary_key_columns([ 'id' ]); __PACKAGE__->meta->foreign_keys ( code => { class => 'Code', key_columns => { fk1 => 'k1', fk2 => 'k2', fk3 => 'k3', }, }, topic => { class => 'Category', key_columns => { topic_id => 'id', }, }, ); __PACKAGE__->meta->relationships ( prices => { class => 'Price', key_columns => { id => 'product_id' }, type => 'one to many', }, ); __PACKAGE__->meta->initialize; 1; See the L section for more discussion of Perl code generation. =item B Auto-initialize the columns (if necessary), then return the Perl source code that is equivalent to the auto-initialization. PARAMS are optional name/value pairs that may include the following: =over 4 =item B The brace style to use in the generated Perl code. STYLE must be either "k&r" or "bsd". The default value is determined by the return value of the L class method. =item B If true, then the generated Perl code will be a method/arguments pair suitable for use as a parameter to L method. The default is false. =item B The integer number of spaces to use for each level of indenting in the generated Perl code. The default value is determined by the return value of the L class method. =back To see examples of the generated code, look in the documentation for the L method. =item B Auto-initialize the foreign keys (if necessary), then return the Perl source code that is equivalent to the auto-initialization. PARAMS are optional name/value pairs that may include the following: =over 4 =item B The brace style to use in the generated Perl code. STYLE must be either "k&r" or "bsd". The default value is determined by the return value of the L class method. =item B If true, then the generated Perl code will be a method/arguments pair suitable for use as a parameter to L method. The default is false. =item B The integer number of spaces to use for each level of indenting in the generated Perl code. The default value is determined by the return value of the L class method. =back To see examples of the generated code, look in the documentation for the L method. =item B Returns a Perl class definition for a L-derived class to manage objects of this L. If a single string is passed, it is taken as the value of the C parameter. PARAMS are optional name/value pairs that may include the following: =over 4 =item B The value of the L parameter that will be passed to the call to L's L method. Defaults to the return value of the L's L method. =item B The name of the manager class. Defaults to the return value of the L's L method. =item B The name of a single class or a reference to an array of class names to be included in the C<@ISA> array for the manager class. One of these classes must inherit from L. Defaults to the return value of the C L. =back For example, given this class: package Product; use Rose::DB::Object; our @ISA = qw(Rose::DB::Object); ... print Product->meta->perl_manager_class( class => 'Prod::Mgr', base_name => 'prod'); The following would be printed: package Prod::Mgr; use Rose::DB::Object::Manager; our @ISA = qw(Rose::DB::Object::Manager); sub object_class { 'Product' } __PACKAGE__->make_manager_methods('prod'); 1; =item B Auto-initialize the primary key column names (if necessary), then return the Perl source code that is equivalent to the auto-initialization. See the larger example in the documentation for the L method to see what the generated Perl code looks like. =item B Auto-initialize the relationships (if necessary), then return the Perl source code that is equivalent to the auto-initialization. PARAMS are optional name/value pairs that may include the following: =over 4 =item B The brace style to use in the generated Perl code. STYLE must be either "k&r" or "bsd". The default value is determined by the return value of the L class method. =item B If true, then the generated Perl code will be a method/arguments pair suitable for use as a parameter to L method. The default is false. =item B The integer number of spaces to use for each level of indenting in the generated Perl code. The default value is determined by the return value of the L class method. =back To see examples of the generated code, look in the documentation for the L method. =item B Auto-initialize the table name (if necessary), then return the Perl source code that is equivalent to the auto-initialization. PARAMS are optional name/value pairs that may include the following: =over 4 =item B The brace style to use in the generated Perl code. STYLE must be either "k&r" or "bsd". The default value is determined by the return value of the L class method. =item B If true, then the generated Perl code will be a method/arguments pair suitable for use as a parameter to L method. The default is false. =item B The integer number of spaces to use for each level of indenting in the generated Perl code. The default value is determined by the return value of the L class method. =back To see examples of the generated code, look in the documentation for the L method. =item B Auto-initialize the unique keys, then return the Perl source code that is equivalent to the auto-initialization. PARAMS are optional name/value pairs that may include the following: =over 4 =item B The brace style to use in the generated Perl code. STYLE must be either "k&r" or "bsd". The default value is determined by the return value of the L class method. =item B If true, then the generated Perl code will be a method/arguments pair suitable for use as a parameter to L method. The default is false. =item B The integer number of spaces to use for each level of indenting in the generated Perl code. The default value is determined by the return value of the L class method. =item B