File Coverage

blib/lib/XML/EasySQL.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             XML::EasySQL - a two-way SQL/XML base class for Perl. It was written by Curtis
5             Lee Fulton (http://fultron.net; curtisf@fultron.net).
6              
7             =head1 VERSION
8              
9             Version 1.2
10              
11             =head1 SYNOPSIS
12              
13             ...
14             # fetch a database row as hash ref
15             my $data = $db->selectrow_hashref('select * from users where id = 2');
16              
17             # init the new EasySQL data object
18             my $data_object = EasySqlChildClass->new({data=>$data});
19              
20             # get the root XML element
21             my $xml = $data_object->getXML();
22              
23             # make changes to the XML document
24             $xml->username->setString('curtisleefulton');
25             $xml->bio->setAttr('age', 22);
26             $xml->bio->city->setString('Portland');
27             $xml->history->access->setAttr('last', time());
28              
29             # output entire XML doc as string to STDOUT
30             print $xml->getDomObj->toString();
31              
32             # update the database
33             my $sql = $data_object->getSQL();
34             my $q = "update users set ".$sql->{users}." where id = 2";
35             $db->do($q);
36              
37             =head1 REQUIREMENTS
38              
39             XML::EasySQL uses XML::DOM. XML::DOM is available from CPAN (www.cpan.org).
40              
41             =head1 DESCRIPTION
42              
43             XML::EasySQL is a two-way SQL/XML base class for Perl. It acts as an emulsifier
44             for the oil and water that is SQL and XML.
45              
46             Features:
47              
48             o Two-way transforms between XML and SQL data
49              
50             o smart SQL updates: only altered tables are updated
51              
52             o unlimited tree depth
53              
54             o multiple SQL tables can merge into one XML tree, then back again
55              
56             o precise control over how data is translated
57              
58             o offers either an easy XML interface or plain DOM
59              
60             o database independent
61              
62             XML::EasySQL works by first taking data spat out by DBI, and turning it into an
63             XML tree. The programmer is then free to modify the data using the easy XML
64             interface that's provided (See XML::EasySQL::XMLnode), or he can start hacking
65             directly on the underlying XML::DOM. When he's ready to dump the changed data
66             back to the database, he only has to call one method.
67              
68             XML::EasySQL is meant to be used as a base class, so it's up to the programmer
69             to cook up the interface details for his data objects.
70              
71             XML::EasySQL consists of two classes: XML::EasySQL and XML::EasySQL::XMLnode.
72              
73             XML::EasySQL is the actual data object class. Its methods transform data
74             between XML and SQL forms. You will use XML::EasySQL as the base class for
75             your data objects.
76              
77             The XML data can be accessed through the XML::EasySQL::XMLnode interface. You
78             probably will be able to use this class as-is, but you have the option of
79             using it as a base class if you need to. See XML::EasySQL::XMLnode for the
80             details.
81              
82             If you end up working directly with XML::DOM, see below for information about
83             the flagSync and flagAttribSync methods. You should also see the section below
84             about the XML::EasySQL schema.
85              
86             XML::EasySQL doesn't provide an SQL interface. It only generates SQL query
87             string fragments for updating the database. XML::EasySQL only accepts a hash
88             ref as database input. It's up to you to write the code that actually touches
89             the database.
90              
91             =head2 Anatomy of an XML::EasySQL derived class
92              
93             Here's a fairly simple class called "User" that's derived from XML::EasySQL:
94              
95             package User;
96             use XML::EasySQL::XMLobj;
97             use XML::EasySQL;
98             @ISA = ('XML::EasySQL');
99              
100             use strict;
101              
102             sub new {
103             my $proto = shift;
104             my $params = shift;
105              
106             # the XML schema string
107             $params->{schema} = q(
108            
109            
110            
111            
112            
113            
114            
115            
116            
117             );
118             my $class = ref($proto) || $proto;
119             my $self = $class->SUPER::new($params);
120             bless $self, $class;
121             }
122              
123             1;
124              
125             The class has inherited all of XML::EasySQL's methods. The constructor passes the XML schema string for the object to it's base class constructor, XML::EasySQL::XMLobj::new
126              
127             So you'd use the User class like this:
128              
129             # fetch the data from the database
130             my $data = $db->selectrow_hashref('select * from users where id = 2');
131             my $comments_data = $db->selectrow_hashref('select * from comments where id =
132             183');
133             $data->{history} = $comments_data->{history};
134             # construct the data object
135             my $user = User->new({data=>$data});
136             # modify the data
137             my $xml = $user->getXML();
138             $xml->username->setString('curtisleefulton');
139             $xml->bio->setAttr('age', 22);
140             $xml->bio->city->setString('Portland');
141             $xml->history->access->setAttr('last', time());
142             # write the changes to the database
143             my $sql = $user->getSQL();
144             my $q = "update users set ".$sql->{users}." where id = 2";
145             $db->do($q);
146             my $q = "update comments set ".$sql->{comments}." where id = 183";
147             $db->do($q);
148              
149             Note that the "User" class, like its parent, still needs its data argument
150             passed through the constructor. You will probably find that too messy and want
151             to make a base class of your own that handles all of the SQL communication, and
152             use that as the base class for all your data objects. For example, a base class
153             called "Base" could go something like this:
154              
155             package Base;
156             use XML::EasySQL::XMLobj;
157             use XML::EasySQL;
158             @ISA = ('XML::EasySQL');
159              
160             use strict;
161              
162             sub new {
163             my $proto = shift;
164             my $params = shift;
165             my $db = $params->{db};
166             my $schema = $params->{$schema};
167             my $data = {};
168             foreach my $table (keys %{$params->{query}}) {
169             my $d = $db->selectrow_hashref("select * from $table
170             where id = ".$params->{query}->{$table});
171             foreach my $k (keys %{$d}) {
172             $data->{$k} = $d->{$k};
173             }
174             }
175              
176             my $class = ref($proto) || $proto;
177             my $self = $class->SUPER::new({data=>$data, schema=>$schema});
178             bless $self, $class;
179             }
180              
181             sub save {
182             my $self = shift;
183             my $sql = $self->getSQL();
184             foreach my $table (keys %{$sql}) {
185             my $q = "update $table set ".$sql->{$table}."
186             where id = ".$self->{query}->{$table};
187             $db->do($q);
188             }
189             }
190              
191             1;
192              
193             So the "User" class could now look something like this:
194              
195             package User;
196             use Base;
197             @ISA = ('Base');
198              
199             use strict;
200              
201             sub new {
202             my $proto = shift;
203             my $params = shift;
204              
205             # the XML schema string
206             $params->{schema} = q(
207            
208            
209            
210            
211            
212            
213            
214            
215            
216             );
217              
218             # get the SQL data
219             $params->{query}->{users} = $params->{user_id};
220             $params->{query}->{comments} = $params->{comment_id};
221              
222             # save the ids
223             $self->{query} = $params->{query};
224             my $class = ref($proto) || $proto;
225             my $self = $class->SUPER::new($params);
226             bless $self, $class;
227             }
228              
229             1;
230              
231             Now that the SQL query is hidden, the "User" object could be constructed this
232             way:
233              
234             my $user = User->new({db=>$db, user_id=>2, comment_id=>183);
235              
236             And to save any changes made to the XML, all is needed is:
237              
238             $user->save();
239              
240             The rest of the interface remains unchanged.
241              
242             If you are writing a large program with many different types of data objects,
243             you'll probably want to make more than one base class.
244              
245             =head2 The XML::EasySQL object schema
246              
247             Every XML::EasySQL object needs an XML schema. The schema tells XML::EasySQL
248             how each column is supposed to map in and out of the XML tree.
249              
250             Here's a simple example:
251              
252            
253              
254             Here's a more complex one:
255              
256            
257            
258            
259            
260            
261            
262            
263            
264            
265              
266              
267             The XML::EasySQL schema can have three root attributes: "name", "default" and
268             "default_table."
269              
270             name - Sets the name of the root XML element. If missing, it defaults to "xml."
271              
272             default - The default type, which controls how incoming SQL column data is
273             processed. If default is missing then XML::EasySQL will ignore SQL columns that
274             aren't specified in the schema. See "type" under "column elements below for
275             more details on the possible types.
276              
277             default_table - The default table a column belongs to. If missing, it defaults
278             to what the "name" attribute is set to.
279              
280             The schema can have multiple column entries. Each entry must have a unique tag
281             name that matches a real column name in an SQL table. Column elements can have
282             two attributes:
283              
284             type - describes how the SQL data will map onto the XML tree. There are three
285             types:
286              
287             o attrib - simply applies the column value as an XML attribute on the root
288             node
289              
290             o string - An XML string that's a child of the root nodea
291              
292             o element - assumes the column value is pure XML. It is parsed into an XML
293             branch and grafted onto the root node of the XML document.
294              
295              
296             table - The table the column belongs to. If missing, it defaults to
297             "default_table."
298              
299             =head1 METHODS
300              
301             =cut
302              
303             package XML::EasySQL;
304 1     1   24418 use XML::EasySQL::XMLobj;
  0            
  0            
305             @ISA = ('XML::EasySQL::XMLobj');
306             use strict;
307              
308             use vars qw/$VERSION/;
309             $VERSION = '1.2';
310              
311             =head2 new (arguments_hash_ref)
312              
313             The XML::EasySQL constructor takes a single anonymous hash reference. It
314             cares about the following keys:
315              
316             data - The SQL data source. Must be a hash ref of column name/value pairs. Use
317             the DBI module and its selectrow_hashref method to generate the hash from
318             your database.
319              
320             schema - The table schema XML string.
321              
322             constructor_class - If you want to use a class derived from
323             XML::EasySQL::XMLnode, specify the class name here. The default is
324             XML::EasySQL::XMLnode.
325              
326             If you're using constructor_class, any additional keys will be passed on to the
327             XML::EasySQL::XMLnode derived class.
328              
329             =cut
330              
331             sub new {
332             my $proto = shift;
333             my $params = shift;
334             my $class = ref($proto) || $proto;
335              
336             if(!defined $params->{constructor_class}) {
337             use XML::EasySQL::XMLnode;
338             $params->{constructor_class} = 'XML::EasySQL::XMLnode';
339             }
340              
341             my $schema = XML::EasySQL::XMLobj->new({type=>'string', param=>$params->{schema}})->root();
342              
343             if(!length $schema->getAttr('name')) {
344             $schema->setAttr('name', 'xml');
345             }
346              
347             my $self = $class->SUPER::new({type=>'new', param=>$schema->getAttr('name'), constructor_class=>$params->{constructor_class}});
348              
349             $self->{schema} = $schema;
350             $self->{data} = $params->{data};
351              
352             $self->{sync_table} = {};
353             $self->{attrib_sync_table} = {};
354              
355             bless $self, $class;
356              
357             $self->{constructor_params}->{db_parent} = $self;
358             $self->_build_xml();
359             return $self;
360             }
361              
362             sub _build_xml {
363             my $self = shift;
364             my $columns = $self->{schema}->columns;
365             my $default = $self->{schema}->getAttr('default');
366             my $data = $self->{data};
367             my $xml = $self->root();
368             foreach my $key (keys %{$data}) {
369             my $type = $columns->getElement($key)->getAttr('type');
370             if(!defined $type || !length $type) {
371             if(defined $default && length $default) {
372             $columns->getElement($key)->setAttr('type', $default);
373             $type = $default;
374             } else {
375             next;
376             }
377             }
378             if(!defined $data->{$key}) {
379             next;
380             }
381             if($type eq 'attrib') {
382             $xml->setAttr($key, $data->{$key});
383             $self->{attrib_sync_table}->{$key} = 0;
384             }
385             if($type eq 'string') {
386             $xml->getElement($key)->setString($data->{$key});
387             $self->{sync_table}->{$key} = 0;
388             }
389             if($type eq 'element') {
390             my $node;
391             if(defined $data->{$key} && length $data->{$key}) {
392             $node = XML::EasySQL::XMLobj->new({type=>'string', param=>$data->{$key}})->root();
393             $node->getDomObj()->setOwnerDocument($xml->getDomObj()->getOwnerDocument());
394             $xml->getDomObj()->appendChild($node->getDomObj());
395             } else {
396             $node = $xml->makeNewNode($key);
397             }
398             $self->{sync_table}->{$key} = 0;
399             }
400             }
401             }
402              
403             =head2 constructorParams ()
404              
405             Returns a hash ref of args. If you're using a derived node class,
406             you can change the args the node constructor gets by modifying this hash.
407              
408             =cut
409              
410             =head2 flagSync (base_name)
411              
412             Flag an XML branch as dirty. Normally flagSync and flagAttribSync are called
413             automatically, but if you've been working on the DOM directly, you'll need to
414             call flagSync yourself, otherwise getSQL won't reflect the changes.
415              
416             arguments:
417              
418             base_name - The base element name of the branch that's been changed. See the
419             schema section in this document for the details on how base branches are
420             configured.
421              
422             =cut
423              
424             sub flagSync {
425             my $self = shift;
426             my $item = shift;
427             if(defined $item) {
428             $self->{sync_table}->{$item} = 1;
429             }
430             }
431              
432             =head2 flagAttribSync (attrib)
433              
434             Flag a root XML attribute as dirty. Normally flagSync and flagAttribSync are
435             called automatically, but if you've been working on the DOM directly, you'll
436             need
437             to call flagAttribSync yourself, otherwise getSQL won't reflect the changes.
438              
439             arguments:
440              
441             attrib - The root attribute name that's been changed. See the schema section in
442             this document for the details on how root attributes are configured.
443              
444             =cut
445              
446             sub flagAttribSync {
447             my $self = shift;
448             my $item = shift;
449             if(defined $item) {
450             $self->{attrib_sync_table}->{$item} = 1;
451             }
452             }
453              
454             =head2 getXML ()
455              
456             Returns the root XML::EasySQL::XMLnode object (Or its derived class.)
457              
458             =cut
459              
460             sub getXML {
461             my $self = shift;
462             return $self->root();
463             }
464              
465             =head2 getSQL (all)
466              
467             Returns a hash ref of partial SQL query strings that can by used to update the
468             database after changes have made to the XML document. Each table affected by
469             the changes has a key in the returned hash ref.
470              
471             arguments:
472              
473             all - If false (default), only changes to the XML will be reflected in the
474             string. If true, a string containing values for all the table columns will
475             result. Note that getSQL resets the accounting each time it's called, so if
476             it's called twice without any changes to the XML in between and arg "all" is
477             false, the second time around the hash ref will be empty.
478              
479             =cut
480              
481             sub getSQL {
482             my $self = shift;
483             my $all = shift;
484              
485             my $q = {};
486             my $sync_table = $self->{sync_table};
487             my $attrib_sync_table = $self->{attrib_sync_table};
488             my $xml = $self->root();
489              
490             my $default_table = $self->{schema}->getAttr('default_table');
491             if(!length $default_table) {
492             $default_table = $self->{schema}->getAttr('name');
493             }
494              
495             foreach my $column ($self->{schema}->columns->getElement()) {
496             my $item = $column->getTagName();
497             my $value = undef;
498             if(!$all && !$attrib_sync_table->{$item} && !$sync_table->{$item}) {
499             next;
500             }
501             my $table_name = $column->getAttr('table');
502             if(!length $table_name) {
503             $table_name = $default_table;
504             }
505             if(!defined $q->{$table_name}) {
506             $q->{$table_name} = '';
507             } else {
508             $q->{$table_name} .= ", ";
509             }
510             if($column->getAttr('type') eq 'attrib') {
511             $value = $xml->getAttr($item);
512             $attrib_sync_table->{$item} = 0;
513             }
514             if($column->getAttr('type') eq 'string') {
515             $value = $xml->getElement($item)->getString();
516             $sync_table->{$item} = 0;
517             }
518             if($column->getAttr('type') eq 'element') {
519             $value = $xml->getElement($item)->getDomObj()->toString();
520             $sync_table->{$item} = 0;
521             }
522             if(!defined $value) {
523             # if we're here then we better remove the trailing
524             # ", "
525             chop $q->{$table_name};
526             chop $q->{$table_name};
527             next;
528             }
529             $q->{$table_name} .= "$item = '$value'";
530             }
531              
532             return $q;
533             }
534              
535             =head1 SEE ALSO
536              
537             DBI
538              
539             XML::DOM
540              
541             XML::EasySQL::XMLnode
542              
543             XML::EasySQL::XMLobj
544              
545             XML::EasySQL::XMLobj::Node
546              
547             =cut
548              
549             1;