=pod =head1 NAME ORM - Object - Relational Mapper =head1 OVERVIEW I have been thinking of a new ORM system which uses a more meta-model centered approach, so that your class defintion itself holds all relevant info. I have only really gotten to the early stages of the design though. But it basically centers around 3 components: 1) A SQL generation engine This part is not related to the class at all, but lives on it's own, and allows you to build structured sets of classes which define a query on your database. The Querys can be of arbitrary complexity and have any number of constraints on them. This information can then be used to not only generate SELECT statements, but also all your UPDATE, DELETE and INSERT operations too. 2) A class meta-model This part is where the metamodel work I have been doing comes into play. Forcing an existing class model to play nice with some kind of persistence layer means you have to add "on top of" the class model. Which means things can get tedious. If you try to hide it behind "magic", then you many times end up limiting the reach of your system (see Class::DBI for a perfect example of this). I suspect that if you design the meta-model from the ground up to be a persistent model, then you can avoid both of these problems. The only drawback is that the user needs to learn and adapt to the new model. However, I think consistency could lessen the learning curve, and powerful enough features will make it compelling enough for people to switch. 3) A persistence engine It is one thing to have a class model which works in a persistence environment, and a cracked out query object. But you still need a system to connect them. This is the DBI of my idea. It is the core which makes both ends work together and deals with all the ugliness of it. This is also the really really hard part :) =head1 SYNOPSIS class Person { # define behaviors method getFullName { my $self = shift; $self->first_name() . " " . $self->last_name; } } where { # this is actually implied # and maybe not needed Query->new(table => 'Person'); }; class Employee is Person { # so when a method returns a query, # it will actually cause it to look # for the object defined by that query # this is all handled in the meta-level method getBoss { my $self = shift; my $boss = Query->new(table => 'Employee'); my $d = Query->new(table => 'Department'); $d->constrain('dept_id' => (equal_to => $self->dept_id())); # lazy join assures it only matches, not folds $boss->lazy_join(manager_employee_id => [ employee_id => $boss ]); return $boss; # Pseudo-SQL this produces: # Employee, Department WHERE ( # (Department.dept_id = $self->dept_id) # AND # (Department.manager_employee_id = Employee.employee_id) # ) } } where { # this is implied by the inheritance my $e = Query->new(table => 'Employee'); my $p = Query->new(table => 'Person'); $e->join(person_id => $p); }; =head1 DESCRIPTION So a class is a combination of a Class defintion (methods, fields, etc) and a Query constraint. class Foo { ... } where { Query->new(table => 'Foo')->constrain(bar => (equal_to => 'baz')) } The resultset of the query defines the set of attributes the class has. Autogenerated accessors and additional annotation about the attributes should also be supported, however, they should not be nessecary. Some Query contraints are implied from a table->class mapping, which will likely be stored in some outside config file. (tbl_person => Person) (tbl_employee => Employee) So Employee isa Person implies a full join of tbl_person and tbl_employee. =head1 FETCHING LARGER DATA SETS my $query = Person->all; # in scalar it will return the lazy Query object, which can be executed later my @people = Person->all; # in list context it fetches lazy People objects my @older_people = Person->all->where(Query->new(table => Person)->constrain('age' => 'greater_than' => 30)); # fetch list of people, but with a filter # you can also add onto a lazy Query's where clause, # each successive call pushes another query onto # a stack to be used to create the SQL my $older_people = $query->where(Query->new(table => Person)->constrain('age' => 'greater_than' => 30)); # then execute it later my @old_people = $query->execute(); # since queries can be modified, they # are highly reusable :) my @sorted_young_people = Person->all ->where( Query->new(table => Person) ->constrain('age' => 'greater_than' => 30) ->group_by('age') ->order_by(Query->ASC) ); This also means that the above (see SYNOPSIS) is better written as: method getManagers { my $self = shift; my $boss_query = Query->new(table => 'Employee'); my $dept = Query->new(table => 'Department'); $dept->constrain('dept_id' => (equal_to => $self->dept_id())); # lazy join assures it only matches, not folds $boss_query->lazy_join(manager_employee_id => [ employee_id => $boss_query ]); return wantarray ? Boss->all->where($boss_query) : $boss_query; } therefore allowing the caller() to determine if the query is executed or not. This also means that we can make things like this: class Employee::Manager { method getEmployeesBy { my ($self, $query) = @_; return Employees->all->where($query) if $query; return Employees->all; } method getAllManagersBy { my ($self, $query) = @_; my $managers = Employees->all->where( Query->new(table => 'Employee') ->constain('role' => (equal_to => 'Manager')) ); $managers->where($query) if $query; return wantarray ? $managers->execute() : $managers; } } =head1 AUTHOR Stevan Little Estevan@iinteractive.comE =cut