DBIx-DBStag
view release on metacpan or search on metacpan
DBIx/DBStag.pm view on Meta::CPAN
if (@nts > 1) {
print STDERR $nt->sxpr;
confess("multiple nodes for: ".$map->sget_fktable);
}
$nt = shift @nts;
if (!$nt) {
print STDERR $map->sxpr;
print STDERR $orig_nt->sxpr;
confess("bad nodes for: ".$map->sget_fktable);
}
}
my $fk_id = $self->_storenode($nt);
if (!defined($fk_id)) {
confess("ASSERTION ERROR: could not get foreign key val\n".
"trying to store: $element\n".
"no fk returned when storing: $fktable");
}
trace(0, "SETTING $element.$col=$fk_id [via ".$orig_nt->element."]") if $TRACE;
$node->set($col, $fk_id);
$node->unset($orig_nt->element);
# do NOT try and expand the value assigned to this
# node with a xort-macro expansion later on
$assigned_node_h{$col} = 1;
trace(0, "ASSIGNED NON-MACRO ID for ".$col) if $TRACE;
}
else {
# 1:many between child and this
# (eg child has fk to this)
# store child after
trace(0, "WILL STORE LATER:\n", $nt->xml) if $TRACE;
$node->unset($nt->element);
push(@delayed_store, $nt);
}
# $node->unset($nt->element); # clear it
}
# --- done storing kids
# --- replace *IDs ---
# dbstag XML allows placeholder values in primary key cols
# (for now, PKs are always assumed to be autoincrement/serial ints)
# placeholder PKs get remapped to a new autogenerated ID
# all FKs referring to this get remapped too
my @tnodes = $node->tnodes; # terminal nodes mapped to columns in db
my %remap = (); # indexed by column name; new PK value
if (!$self->trust_primary_key_values) {
foreach my $tnode (@tnodes) {
# foreign keys in XORT mode - replace macro ID with
# actual database foreign key value
if ($self->is_fk_col($tnode->name) && $self->xort_mode) {
my $v = $tnode->data;
# -- CHECK FOR MACRO EXPANSION (XORT-STYLE) --
# IF this tnode was originally an ntnode that
# was collapsed to a pk val, xort style, do not
# try and map it to a previously assigned macro
# EXAMPLE:
# we start with <foo_id><bar><key>A</></></>
# we collapse too <foo_id>$v</>
if ($assigned_node_h{$tnode->name}) {
trace(0, "ALREADY CALCULATED; not a Macro ID:$v;; in $element/".$tnode->name) if $TRACE;
# DO NOTHING
}
else { # NOT ASSIGNED
my $actual_id =
$self->macro_id_h->{$v};
if (!defined($actual_id)) {
$self->throw("XORT-style Macro ID:$v is undefined;; in $element/".$tnode->name);
}
$tnode->data($actual_id);
}
# -- END OF MACRO EXPANSION --
}
elsif ($tnode->name eq $pkcol) {
my $v = $tnode->data;
trace(0, "REMAP $pkcol: $v => ? [do not know new value yet]") if $TRACE;
$remap{$tnode->name} = $v; # map after insert/update
$node->unset($tnode->name); # discard placeholder
} else {
if ($self->is_fk_col($tnode->name)) {
# hack!! need proper FK refs...; DBSchema wont do this
my $colvalmap = $self->id_remap_idx;
#my $colvalmap = $self->get_mapping_for_col($nt->elememt);
if ($colvalmap) {
my $v = $tnode->data;
my $nv = $colvalmap->{$v};
if ($nv) {
trace(0, "remapping $v => $nv") if $TRACE;
$tnode->data($nv);
}
}
}
}
}
} # -- end of ID remapping
# --- Get columns that need updating/inserting ---
# turn all remaining tag-val pairs into a hash
my %store_hash = $node->pairs;
# All columns to be stored should be terminal nodes
# in the Stag tree; if not there is a problem
my @refcols = grep { ref($store_hash{$_}) } keys %store_hash;
if (@refcols) {
foreach (@$mapping) {
trace(0, $_->sxpr) if $TRACE;
}
confess("I can't store the current node; ".
"These elements need to be mapped via FKs: ".
join(', ', map {"\"@refcols\""} @refcols).
"\n\nPerhaps you need to specify more schema metadata?");
} # -- end of sanity check
# each relation has zero or more unique keys;
# unique keys may be compound (ie >1 column)
my @usets = $self->get_unique_sets($element);
trace(0, "USETS: ", map {"unique[ @$_ ]"} @usets) if $TRACE;
# get all the columns/fields/attributes of this relation
my @cols = $self->get_all_cols($element);
trace(0, "COLS: @cols") if $TRACE;
# store_node() will either perform an update or
# an insert. if we are performing an update, we
# need a query constraint to determine which row
# to update.
#
# this hash is used to determine the key/val pairs
DBIx/DBStag.pm view on Meta::CPAN
The B<address> node will be stored in the database and collapsed to
whatever the value of the primary key is.
=head3 Stag-style mapping
Stag-style is more compact, but sometimes relies on the presence of a
B<dbstag_metadata> element to specify how foreign keys are mapped
=head3 Operations
Operations are specified as attributes inside elements, specifying
whether the nod should be inserted, updated, looked up or
stored/forced. Operations are optional (default is force/store).
<person op="insert">
<name>fred</name>
<address_id op="lookup">
<streetaddr>..</>
<city>..</>
</address_id>
</person>
The above will always insert into the person table (which may be quite
dangerous; if an entry with the same unique constraint exists, an
error will be thrown). Assuming (streetaddr,city) is a unique
constraint for the address table, this will lookup the specified
address (and not modify the table) and use the returned pk value for
the B<person.address_id> foreign key
The operations are:
=over
=item force (default)
looks up (by unique constraints) first; if exists, will do an
update. if does not exist, will do an insert
=item insert
insert only. DBMS will throw error if row with same UC exists
=item update
update only. DBMS will throw error if a row the with the specified UC
cannot be found
=item lookup
finds the pk value using one of the unique constraints present in the
XML node
=item delete NOT IMPLEMENTED
deletes row that has matching UC
=back
Operations can be used in either XORT or Stag mode
=head3 Macros
Macros can be used with either XORT or Stag style mappings. Macros
allow you to refer to the same node later on in the XML
<person op="lookup" id="joe">
<name>joe</name>
</person>
<person op="lookup" id="fred">
<name>fred</name>
</person>
...
<person_relationship>
<type>friend</type>
<person1_id>joe</person1_id>
<person2_id>fred</person2_id>
</person_relationship>
Assuming B<name> is a unique constraint for B<person>, and
person_relationship has two foreign keys named person1_id and
person2_id linking to the person table, DBStag will first lookup the
two person rows by name (throwing an error if not present) and use the
returned pk values to populate the person_relationship table
=head3 How it works
Before a node is stored, certain subnodes will be pre-stored; these are
subnodes for which there is a foreign key mapping FROM the parent node
TO the child node. This pre-storage is recursive.
After these nodes are stored, the current node is either INSERTed or
UPDATEd. The database is introspected for UNIQUE constraints; these
are used as keys. If there exists a row in the database with matching
key, then the node is UPDATEd; otherwise it is INSERTed.
(primary keys from pre-stored nodes become foreign key values in the
existing node)
Subsequently, all subnodes that were not pre-stored are now
post-stored. The primary key for the existing node will become
foreign keys for the post-stored subnodes.
=head2 force_safe_node_names
Usage - $dbh->force_safe_node_names(1);
Returns - bool
Args - bool [optional]
If this is set, then before storage, all node names are made
B<DB-safe>; they are lowercased, and the following transform is
applied:
tr/a-z0-9_//cd;
=head2 mapping
Usage - $dbh->mapping(["alias/table.col=fktable.fkcol"]);
Returns -
Args - array
Creates a stag-relational mapping (for storing data only)
Occasionally not enough information can be obtained from db
DBIx/DBStag.pm view on Meta::CPAN
named "dbstag_metadata" will not be loaded; it is used to supply the
mapping. For example:
<personset>
<dbstag_mapping>
<map>favourite_film/person.favourite_film_id=film.film_id</map>
<map>least_favourite_film/person.least_favourite_film_id=film.film_id</map>
</dbstag_mapping>
<person>...
=head2 mapconf
Usage - $dbh->mapconf("mydb-stagmap.stm");
Returns -
Args - filename
sets the conf file containing the stag-relational mappings
This is not of any use for a XORT-style mapping, where foreign key
columns are explicitly stated
See mapping() above
The file contains line like:
favourite_film/person.favourite_film_id=film.film_id
least_favourite_film/person.least_favourite_film_id=film.film_id
=head2 noupdate_h
Usage - $dbh->noupdate_h({person=>1})
Returns -
Args - hashref
Keys of hash are names of nodes that do not get updated - if a unique
key is queried for and does not exist, the node will be inserted and
subnodes will be stored; if the unique key does exist in the db, then
this will not be updated; subnodes will not be stored
=head2 trust_primary_key_values
Usage - $dbh->trust_primary_key_values(1)
Returns - bool
Args - bool (optional)
The default behaviour of the storenode() method is to remap all
B<surrogate> PRIMARY KEY values it comes across.
A surrogate primary key is typically a primary key of type SERIAL (or
AUTO_INCREMENT) in MySQL. They are identifiers assigned automatically
be the database with no semantics.
It may be desirable to store the same data in two different
databases. We would generally not expect the surrogate IDs to match
between databases, even if the rest of the data does.
(If you do not use surrogate primary key columns in your load xml,
then you can ignore this accessor)
You should NOT use this method in conjunction with Macros
If you use primary key columns in your XML, and the primary keys are
not surrogate, then youshould set this. If this accessor is set to
non-zero (true) then the primary key values in the XML will be used.
If your db has surrogate/auto-increment/serial PKs, and you wish to
use these PK columns in your XML, yet you want to make XML that can be
exported from one db and imported into another, then the default
behaviour will be fine.
For example, if we extract a 'person' from a db with surrogate PK
B<id> and unique key B<ssno>, we may get this:
<person>
<id>23</id>
<name>fred</name>
<ssno>1234-567</ssno>
</person>
If we then import this into an entirely fresh db, with no rows in
table B<person>, then the default behaviour of storenode() will create a
row like this:
<person>
<id>1</id>
<name>fred</name>
<ssno>1234-567</ssno>
</person>
The PK val 23 has been mapped to 1 (all foreign keys that point to
person.id=23 will now point to person.id=1)
If we were to first call $sdbh->trust_primary_key_values(1), then
person.id would remain to be 23. This would only be appropriate
behaviour if we were storing back into the same db we retrieved from.
=head2 tracenode
Usage - $dbh->tracenode('person/name')
Traces on STDERR inserts/updates on a particular element type (table),
displaying the sub-element (column value).
=head2 is_caching_on B<ADVANCED OPTION>
Usage - $dbh->is_caching_on('person', 1)
Returns - number
Args - number
0: off (default)
1: memory-caching ON
2: memory-caching OFF, bulkload ON
3: memory-caching ON, bulkload ON
IN-MEMORY CACHING
By default no in-memory caching is used. If this is set to 1,
then an in-memory cache is used for any particular element. No cache
management is used, so you should be sure not to cache elements that
will cause memory overloads.
( run in 0.943 second using v1.01-cache-2.11-cpan-bbe5e583499 )