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 )