11 |
use Time::HiRes qw(gettimeofday); |
use Time::HiRes qw(gettimeofday); |
12 |
use Digest::MD5 qw(md5_base64); |
use Digest::MD5 qw(md5_base64); |
13 |
use CGI; |
use CGI; |
14 |
|
use WikiTools; |
15 |
|
|
16 |
=head1 Entity-Relationship Database Package |
=head1 Entity-Relationship Database Package |
17 |
|
|
390 |
Entities => 'Entity', |
Entities => 'Entity', |
391 |
Fields => 'Field', |
Fields => 'Field', |
392 |
Indexes => 'Index', |
Indexes => 'Index', |
393 |
IndexFields => 'IndexField' |
IndexFields => 'IndexField', |
394 |
|
Issues => 'Issue', |
395 |
|
Shapes => 'Shape' |
396 |
}, |
}, |
397 |
KeyAttr => { Relationship => 'name', |
KeyAttr => { Relationship => 'name', |
398 |
Entity => 'name', |
Entity => 'name', |
399 |
Field => 'name' |
Field => 'name', |
400 |
|
Shape => 'name' |
401 |
}, |
}, |
402 |
SuppressEmpty => 1, |
SuppressEmpty => 1, |
403 |
); |
); |
404 |
|
|
405 |
my %XmlInOpts = ( |
my %XmlInOpts = ( |
406 |
ForceArray => ['Field', 'Index', 'IndexField', 'Relationship', 'Entity'], |
ForceArray => [qw(Field Index IndexField Relationship Entity Shape)], |
407 |
ForceContent => 1, |
ForceContent => 1, |
408 |
NormalizeSpace => 2, |
NormalizeSpace => 2, |
409 |
); |
); |
412 |
XMLDecl => 1, |
XMLDecl => 1, |
413 |
); |
); |
414 |
|
|
|
|
|
415 |
=head2 Public Methods |
=head2 Public Methods |
416 |
|
|
417 |
=head3 new |
=head3 new |
436 |
|
|
437 |
sub new { |
sub new { |
438 |
# Get the parameters. |
# Get the parameters. |
439 |
my ($class, $dbh, $metaFileName, $options) = @_; |
my ($class, $dbh, $metaFileName, %options) = @_; |
440 |
# Load the meta-data. |
# Load the meta-data. |
441 |
my $metaData = _LoadMetaData($metaFileName); |
my $metaData = _LoadMetaData($metaFileName); |
442 |
# Create the object. |
# Create the object. |
660 |
return Data::Dumper::Dumper($self->{_metaData}); |
return Data::Dumper::Dumper($self->{_metaData}); |
661 |
} |
} |
662 |
|
|
663 |
|
=head3 GenerateWikiData |
664 |
|
|
665 |
|
my @wikiLines = $erdb->GenerateWikiData(); |
666 |
|
|
667 |
|
Build a description of the database for the wiki. The database will be |
668 |
|
organized into a single page, with sections for each entity and relationship. |
669 |
|
The return value is a list of text lines. |
670 |
|
|
671 |
|
=cut |
672 |
|
|
673 |
|
sub GenerateWikiData { |
674 |
|
# Get the parameters. |
675 |
|
my ($self) = @_; |
676 |
|
# We'll build the wiki text in here. |
677 |
|
my @retVal = (); |
678 |
|
# Get the metadata object. |
679 |
|
my $metadata = $self->{_metaData}; |
680 |
|
# Get the title string. This will become the page name. |
681 |
|
my $title = $metadata->{Title}->{content}; |
682 |
|
# Get the entity and relationship lists. |
683 |
|
my $entityList = $metadata->{Entities}; |
684 |
|
my $relationshipList = $metadata->{Relationships}; |
685 |
|
my $shapeList = $metadata->{Shapes}; |
686 |
|
# Start with the introductory text. |
687 |
|
push @retVal, WikiTools::Heading(2, "Introduction"); |
688 |
|
if (my $notes = $metadata->{Notes}) { |
689 |
|
push @retVal, WikiNote($notes->{content}); |
690 |
|
} |
691 |
|
# Generate issue list. |
692 |
|
if (my $issues = $metadata->{Issues}) { |
693 |
|
push @retVal, WikiTools::Heading(3, 'Issues'); |
694 |
|
push @retVal, WikiTools::List(map { $_->{content} } @{$issues}); |
695 |
|
} |
696 |
|
# Start the entity section. |
697 |
|
push @retVal, WikiTools::Heading(2, "Entities"); |
698 |
|
# Loop through the entities. Note that unlike the situation with HTML, we |
699 |
|
# don't need to generate the table of contents manually, just the data |
700 |
|
# itself. |
701 |
|
for my $key (sort keys %$entityList) { |
702 |
|
# Create a header for this entity. |
703 |
|
push @retVal, "", WikiTools::Heading(3, $key); |
704 |
|
# Get the entity data. |
705 |
|
my $entityData = $entityList->{$key}; |
706 |
|
# Plant the notes here, if there are any. |
707 |
|
push @retVal, _ObjectNotes($entityData); |
708 |
|
# Now we list the entity's relationships (if any). First, we build a list |
709 |
|
# of the relationships relevant to this entity. |
710 |
|
my @rels = (); |
711 |
|
for my $rel (sort keys %$relationshipList) { |
712 |
|
my $relStructure = $relationshipList->{$rel}; |
713 |
|
if ($relStructure->{from} eq $key || $relStructure->{to} eq $key) { |
714 |
|
# Get the relationship sentence. |
715 |
|
my $relSentence = _ComputeRelationshipSentence($rel, $relStructure); |
716 |
|
# Linkify it. |
717 |
|
my $linkedRel = WikiTools::LinkMarkup("#$rel", $rel); |
718 |
|
$relSentence =~ s/$rel/$linkedRel/; |
719 |
|
push @rels, $relSentence; |
720 |
|
} |
721 |
|
} |
722 |
|
# Add the relationships as a Wiki list. |
723 |
|
push @retVal, WikiTools::List(@rels); |
724 |
|
# Get the entity's relations. |
725 |
|
my $relationList = $entityData->{Relations}; |
726 |
|
# Loop through the relations, displaying them. |
727 |
|
for my $relation (sort keys %{$relationList}) { |
728 |
|
my $wikiString = _WikiRelationTable($relation, $relationList->{$relation}); |
729 |
|
push @retVal, $wikiString; |
730 |
|
} |
731 |
|
} |
732 |
|
# Now the entities are documented. Next we do the relationships. |
733 |
|
push @retVal, WikiTools::Heading(2, "Relationships"); |
734 |
|
for my $key (sort keys %$relationshipList) { |
735 |
|
my $relationshipData = $relationshipList->{$key}; |
736 |
|
# Create the relationship heading. |
737 |
|
push @retVal, WikiTools::Heading(3, $key); |
738 |
|
# Describe the relationship arity. Note there's a bit of trickiness involving recursive |
739 |
|
# many-to-many relationships. In a normal many-to-many we use two sentences to describe |
740 |
|
# the arity (one for each direction). This is a bad idea for a recursive relationship, |
741 |
|
# since both sentences will say the same thing. |
742 |
|
my $arity = $relationshipData->{arity}; |
743 |
|
my $fromEntity = $relationshipData->{from}; |
744 |
|
my $toEntity = $relationshipData->{to}; |
745 |
|
my @listElements = (); |
746 |
|
my $boldCode = WikiTools::BoldCode(); |
747 |
|
if ($arity eq "11") { |
748 |
|
push @listElements, "Each $boldCode$fromEntity$boldCode relates to at most one $boldCode$toEntity$boldCode."; |
749 |
|
} else { |
750 |
|
push @listElements, "Each $boldCode$fromEntity$boldCode relates to multiple $boldCode${toEntity}s$boldCode."; |
751 |
|
if ($arity eq "MM" && $fromEntity ne $toEntity) { |
752 |
|
push @listElements, "Each $boldCode$toEntity$boldCode relates to multiple $boldCode${fromEntity}s$boldCode."; |
753 |
|
} |
754 |
|
} |
755 |
|
push @retVal, WikiTools::List(@listElements); |
756 |
|
# Plant the notes here, if there are any. |
757 |
|
push @retVal, _ObjectNotes($relationshipData); |
758 |
|
# Finally, the relationship table. |
759 |
|
my $wikiString = _WikiRelationTable($key, $relationshipData->{Relations}->{$key}); |
760 |
|
push @retVal, $wikiString; |
761 |
|
} |
762 |
|
# Now loop through the miscellaneous shapes. |
763 |
|
if ($shapeList) { |
764 |
|
push @retVal, WikiTools::Heading(2, "Miscellaneous"); |
765 |
|
for my $shape (sort keys %$shapeList) { |
766 |
|
push @retVal, WikiTools::Heading(3, $shape); |
767 |
|
my $shapeData = $shapeList->{$shape}; |
768 |
|
push @retVal, _ObjectNotes($shapeData); |
769 |
|
} |
770 |
|
} |
771 |
|
# All done. Return the lines. |
772 |
|
return @retVal; |
773 |
|
} |
774 |
|
|
775 |
|
|
776 |
=head3 CreatePPO |
=head3 CreatePPO |
777 |
|
|
778 |
ERDB::CreatePPO($erdbXMLFile, $ppoXMLFile); |
ERDB::CreatePPO($erdbXMLFile, $ppoXMLFile); |
1062 |
# Push the result into the field list. |
# Push the result into the field list. |
1063 |
push @fieldList, $fieldString; |
push @fieldList, $fieldString; |
1064 |
} |
} |
|
# If this is a root table, add the "new_record" flag. It defaults to 0, so |
|
|
if ($rootFlag) { |
|
|
push @fieldList, "new_record $TypeTable{boolean}->{sqlType} NOT NULL DEFAULT 0"; |
|
|
} |
|
1065 |
# Convert the field list into a comma-delimited string. |
# Convert the field list into a comma-delimited string. |
1066 |
my $fieldThing = join(', ', @fieldList); |
my $fieldThing = join(', ', @fieldList); |
1067 |
# Insure the table is not already there. |
# Insure the table is not already there. |
1528 |
return sort keys %{$entityList}; |
return sort keys %{$entityList}; |
1529 |
} |
} |
1530 |
|
|
1531 |
|
|
1532 |
|
=head3 GetConnectingRelationships |
1533 |
|
|
1534 |
|
my @list = $erdb->GetConnectingRelationships($entityName); |
1535 |
|
|
1536 |
|
Return a list of the relationships connected to the specified entity. |
1537 |
|
|
1538 |
|
=over 4 |
1539 |
|
|
1540 |
|
=item entityName |
1541 |
|
|
1542 |
|
Entity whose connected relationships are desired. |
1543 |
|
|
1544 |
|
=item RETURN |
1545 |
|
|
1546 |
|
Returns a list of the relationships that originate from the entity. |
1547 |
|
If the entity is on the from end, it will return the relationship |
1548 |
|
name. If the entity is on the to end it will return the converse of |
1549 |
|
the relationship name. |
1550 |
|
|
1551 |
|
=back |
1552 |
|
|
1553 |
|
=cut |
1554 |
|
|
1555 |
|
sub GetConnectingRelationships { |
1556 |
|
# Get the parameters. |
1557 |
|
my ($self, $entityName) = @_; |
1558 |
|
# Declare the return variable. |
1559 |
|
my @retVal; |
1560 |
|
# Get the relationship list. |
1561 |
|
my $relationships = $self->{_metaData}->{Relationships}; |
1562 |
|
# Find the entity. |
1563 |
|
my $entity = $self->{_metaData}->{Entities}->{$entityName}; |
1564 |
|
# Only proceed if the entity exists. |
1565 |
|
if (! defined $entity) { |
1566 |
|
Trace("Entity $entityName not found.") if T(3); |
1567 |
|
} else { |
1568 |
|
# Loop through the relationships. |
1569 |
|
my @rels = keys %$relationships; |
1570 |
|
Trace(scalar(@rels) . " relationships found in connection search.") if T(3); |
1571 |
|
for my $relationshipName (@rels) { |
1572 |
|
my $relationship = $relationships->{$relationshipName}; |
1573 |
|
if ($relationship->{from} eq $entityName) { |
1574 |
|
# Here we have a forward relationship. |
1575 |
|
push @retVal, $relationshipName; |
1576 |
|
} elsif ($relationship->{to} eq $entityName) { |
1577 |
|
# Here we have a backward relationship. In this case, the |
1578 |
|
# converse relationship name is preferred if it exists. |
1579 |
|
my $converse = $relationship->{converse} || $relationshipName; |
1580 |
|
push @retVal, $converse; |
1581 |
|
} |
1582 |
|
} |
1583 |
|
} |
1584 |
|
# Return the result. |
1585 |
|
return @retVal; |
1586 |
|
} |
1587 |
|
|
1588 |
|
|
1589 |
=head3 GetDataTypes |
=head3 GetDataTypes |
1590 |
|
|
1591 |
my %types = ERDB::GetDataTypes(); |
my %types = ERDB::GetDataTypes(); |
2169 |
# Loop through the ends of the relationship. |
# Loop through the ends of the relationship. |
2170 |
for my $dir ('from', 'to') { |
for my $dir ('from', 'to') { |
2171 |
if ($structure->{$dir} eq $originEntityName) { |
if ($structure->{$dir} eq $originEntityName) { |
|
# Delete all relationship instances on this side of the entity instance. |
|
|
Trace("Disconnecting in $dir direction with ID \"$originEntityID\"."); |
|
|
$dbh->SQL("DELETE FROM $relationshipName WHERE ${dir}_link = ?", 0, $originEntityID); |
|
2172 |
$found = 1; |
$found = 1; |
2173 |
|
# Here we want to delete all relationship instances on this side of the |
2174 |
|
# entity instance. |
2175 |
|
Trace("Disconnecting in $dir direction with ID \"$originEntityID\"."); |
2176 |
|
# We do this delete in batches to keep it from dragging down the |
2177 |
|
# server. |
2178 |
|
my $limitClause = ($FIG_Config::delete_limit ? "LIMIT $FIG_Config::delete_limit" : ""); |
2179 |
|
my $done = 0; |
2180 |
|
while (! $done) { |
2181 |
|
# Do the delete. |
2182 |
|
my $rows = $dbh->SQL("DELETE FROM $relationshipName WHERE ${dir}_link = ? $limitClause", 0, $originEntityID); |
2183 |
|
# See if we're done. We're done if no rows were found or the delete is unlimited. |
2184 |
|
$done = ($rows == 0 || ! $limitClause); |
2185 |
|
} |
2186 |
} |
} |
2187 |
} |
} |
2188 |
# Insure we found the entity on at least one end. |
# Insure we found the entity on at least one end. |
2370 |
} |
} |
2371 |
# Now we parse the key names into sort parameters. First, we prime the return |
# Now we parse the key names into sort parameters. First, we prime the return |
2372 |
# string. |
# string. |
2373 |
my $retVal = "sort -t\"\t\" "; |
my $retVal = "sort -S 1G -T\"$FIG_Config::temp\" -t\"\t\" "; |
2374 |
# Get the relation's field list. |
# Get the relation's field list. |
2375 |
my @fields = @{$relationData->{Fields}}; |
my @fields = @{$relationData->{Fields}}; |
2376 |
# Loop through the keys. |
# Loop through the keys. |
2400 |
# will stop the inner loop. Note that the field number is |
# will stop the inner loop. Note that the field number is |
2401 |
# 1-based in the sort command, so we have to increment the |
# 1-based in the sort command, so we have to increment the |
2402 |
# index. |
# index. |
2403 |
$fieldSpec = ($i + 1) . $modifier; |
my $realI = $i + 1; |
2404 |
|
$fieldSpec = "$realI,$realI$modifier"; |
2405 |
} |
} |
2406 |
} |
} |
2407 |
# Add this field to the sort command. |
# Add this field to the sort command. |
2792 |
push @missing, $fieldName; |
push @missing, $fieldName; |
2793 |
} |
} |
2794 |
} |
} |
|
# If we are the primary relation, add the new-record flag. |
|
|
if ($relationName eq $newObjectType) { |
|
|
push @valueList, 1; |
|
|
push @fieldNameList, "new_record"; |
|
|
} |
|
2795 |
# Only proceed if there are no missing fields. |
# Only proceed if there are no missing fields. |
2796 |
if (@missing > 0) { |
if (@missing > 0) { |
2797 |
Trace("Relation $relationName for $newObjectType skipped due to missing fields: " . |
Trace("Relation $relationName for $newObjectType skipped due to missing fields: " . |
2899 |
|
|
2900 |
=head3 LoadTable |
=head3 LoadTable |
2901 |
|
|
2902 |
my $results = $erdb->LoadTable($fileName, $relationName, $truncateFlag); |
my $results = $erdb->LoadTable($fileName, $relationName, %options); |
2903 |
|
|
2904 |
Load data from a tab-delimited file into a specified table, optionally re-creating the table |
Load data from a tab-delimited file into a specified table, optionally re-creating the table |
2905 |
first. |
first. |
2914 |
|
|
2915 |
Name of the relation to be loaded. This is the same as the table name. |
Name of the relation to be loaded. This is the same as the table name. |
2916 |
|
|
2917 |
=item truncateFlag |
=item options |
2918 |
|
|
2919 |
TRUE if the table should be dropped and re-created, else FALSE |
A hash of load options. |
2920 |
|
|
2921 |
=item RETURN |
=item RETURN |
2922 |
|
|
2924 |
|
|
2925 |
=back |
=back |
2926 |
|
|
2927 |
|
The permissible options are as follows. |
2928 |
|
|
2929 |
|
=over 4 |
2930 |
|
|
2931 |
|
=item truncate |
2932 |
|
|
2933 |
|
If TRUE, then the table will be erased before loading. |
2934 |
|
|
2935 |
|
=item mode |
2936 |
|
|
2937 |
|
Mode in which the load should operate, either C<low_priority> or C<concurrent>. |
2938 |
|
This option is only applicable to a MySQL database. |
2939 |
|
|
2940 |
|
=item partial |
2941 |
|
|
2942 |
|
If TRUE, then it is assumed that this is a partial load, and the table will not |
2943 |
|
be analyzed and compacted at the end. |
2944 |
|
|
2945 |
|
=back |
2946 |
|
|
2947 |
=cut |
=cut |
2948 |
sub LoadTable { |
sub LoadTable { |
2949 |
# Get the parameters. |
# Get the parameters. |
2950 |
my ($self, $fileName, $relationName, $truncateFlag) = @_; |
my ($self, $fileName, $relationName, %options) = @_; |
2951 |
# Create the statistical return object. |
# Create the statistical return object. |
2952 |
my $retVal = _GetLoadStats(); |
my $retVal = _GetLoadStats(); |
2953 |
# Trace the fact of the load. |
# Trace the fact of the load. |
2959 |
# Get the relation data. |
# Get the relation data. |
2960 |
my $relation = $self->_FindRelation($relationName); |
my $relation = $self->_FindRelation($relationName); |
2961 |
# Check the truncation flag. |
# Check the truncation flag. |
2962 |
if ($truncateFlag) { |
if ($options{truncate}) { |
2963 |
Trace("Creating table $relationName") if T(2); |
Trace("Creating table $relationName") if T(2); |
2964 |
# Compute the row count estimate. We take the size of the load file, |
# Compute the row count estimate. We take the size of the load file, |
2965 |
# divide it by the estimated row size, and then multiply by 2 to |
# divide it by the estimated row size, and then multiply by 2 to |
2985 |
# Load the table. |
# Load the table. |
2986 |
my $rv; |
my $rv; |
2987 |
eval { |
eval { |
2988 |
$rv = $dbh->load_table(file => $fileName, tbl => $relationName); |
$rv = $dbh->load_table(file => $fileName, tbl => $relationName, style => $options{mode}); |
2989 |
}; |
}; |
2990 |
if (!defined $rv) { |
if (!defined $rv) { |
2991 |
$retVal->AddMessage($@) if ($@); |
$retVal->AddMessage($@) if ($@); |
2996 |
$retVal->Add("tables"); |
$retVal->Add("tables"); |
2997 |
my $size = -s $fileName; |
my $size = -s $fileName; |
2998 |
Trace("$size bytes loaded into $relationName.") if T(2); |
Trace("$size bytes loaded into $relationName.") if T(2); |
2999 |
|
$retVal->Add("bytes", $size); |
3000 |
# If we're rebuilding, we need to create the table indexes. |
# If we're rebuilding, we need to create the table indexes. |
3001 |
if ($truncateFlag) { |
if ($options{truncate}) { |
3002 |
# Indexes are created here for PostGres. For PostGres, indexes are |
# Indexes are created here for PostGres. For PostGres, indexes are |
3003 |
# best built at the end. For MySQL, the reverse is true. |
# best built at the end. For MySQL, the reverse is true. |
3004 |
if (! $dbh->{_preIndex}) { |
if (! $dbh->{_preIndex}) { |
3019 |
} |
} |
3020 |
} |
} |
3021 |
# Analyze the table to improve performance. |
# Analyze the table to improve performance. |
3022 |
|
if (! $options{partial}) { |
3023 |
Trace("Analyzing and compacting $relationName.") if T(3); |
Trace("Analyzing and compacting $relationName.") if T(3); |
3024 |
$dbh->vacuum_it($relationName); |
$self->Analyze($relationName); |
3025 |
|
} |
3026 |
Trace("$relationName load completed.") if T(3); |
Trace("$relationName load completed.") if T(3); |
3027 |
# Return the statistics. |
# Return the statistics. |
3028 |
return $retVal; |
return $retVal; |
3029 |
} |
} |
3030 |
|
|
3031 |
|
=head3 Analyze |
3032 |
|
|
3033 |
|
$erdb->Analyze($tableName); |
3034 |
|
|
3035 |
|
Analyze and compact a table in the database. This is useful after a load |
3036 |
|
to improve the performance of the indexes. |
3037 |
|
|
3038 |
|
=over 4 |
3039 |
|
|
3040 |
|
=item tableName |
3041 |
|
|
3042 |
|
Name of the table to be analyzed and compacted. |
3043 |
|
|
3044 |
|
=back |
3045 |
|
|
3046 |
|
=cut |
3047 |
|
|
3048 |
|
sub Analyze { |
3049 |
|
# Get the parameters. |
3050 |
|
my ($self, $tableName) = @_; |
3051 |
|
# Analyze the table. |
3052 |
|
$self->{_dbh}->vacuum_it($tableName); |
3053 |
|
} |
3054 |
|
|
3055 |
|
=head3 TruncateTable |
3056 |
|
|
3057 |
|
$erdb->TruncateTable($table); |
3058 |
|
|
3059 |
|
Delete all rows from a table quickly. This uses the built-in SQL |
3060 |
|
C<TRUNCATE> statement, which effectively drops and re-creates a table |
3061 |
|
with all its settings intact. |
3062 |
|
|
3063 |
|
=over 4 |
3064 |
|
|
3065 |
|
=item table |
3066 |
|
|
3067 |
|
Name of the table to be cleared. |
3068 |
|
|
3069 |
|
=back |
3070 |
|
|
3071 |
|
=cut |
3072 |
|
|
3073 |
|
sub TruncateTable { |
3074 |
|
# Get the parameters. |
3075 |
|
my ($self, $table) = @_; |
3076 |
|
# Get the database handle. |
3077 |
|
my $dbh = $self->{_dbh}; |
3078 |
|
# Execute a truncation comment. |
3079 |
|
$dbh->SQL("TRUNCATE TABLE $table"); |
3080 |
|
} |
3081 |
|
|
3082 |
|
|
3083 |
=head3 CreateSearchIndex |
=head3 CreateSearchIndex |
3084 |
|
|
3085 |
$erdb->CreateSearchIndex($objectName); |
$erdb->CreateSearchIndex($objectName); |
3260 |
my $query = $self->Get([$entityType], "$entityType(id) = ?", [$ID]); |
my $query = $self->Get([$entityType], "$entityType(id) = ?", [$ID]); |
3261 |
# Get the first (and only) object. |
# Get the first (and only) object. |
3262 |
my $retVal = $query->Fetch(); |
my $retVal = $query->Fetch(); |
3263 |
|
if (T(3)) { |
3264 |
|
if ($retVal) { |
3265 |
|
Trace("Entity $entityType \"$ID\" found."); |
3266 |
|
} else { |
3267 |
|
Trace("Entity $entityType \"$ID\" not found."); |
3268 |
|
} |
3269 |
|
} |
3270 |
# Return the result. |
# Return the result. |
3271 |
return $retVal; |
return $retVal; |
3272 |
} |
} |
3459 |
push @retVal, \@rowData; |
push @retVal, \@rowData; |
3460 |
$fetched++; |
$fetched++; |
3461 |
} |
} |
|
Trace("$fetched rows returned in GetAll.") if T(SQL => 4); |
|
3462 |
# Return the resulting list. |
# Return the resulting list. |
3463 |
return @retVal; |
return @retVal; |
3464 |
} |
} |
3815 |
return $retVal; |
return $retVal; |
3816 |
} |
} |
3817 |
|
|
3818 |
|
=head3 WikiNote |
3819 |
|
|
3820 |
|
Convert a note or comment to Wiki text by replacing some bulletin-board codes with HTML. The codes |
3821 |
|
supported are C<[b]> for B<bold>, C<[i]> for I<italics>, and C<[p]> for a new paragraph. |
3822 |
|
Except for C<[p]>, all the codes are closed by slash-codes. So, for |
3823 |
|
example, C<[b]Feature[/b]> displays the string C<Feature> in boldface. |
3824 |
|
|
3825 |
|
my $wikiText = ERDB::WikiNote($dataString); |
3826 |
|
|
3827 |
|
=over 4 |
3828 |
|
|
3829 |
|
=item dataString |
3830 |
|
|
3831 |
|
String to convert to Wiki text. |
3832 |
|
|
3833 |
|
=item RETURN |
3834 |
|
|
3835 |
|
An Wiki text string derived from the input string. |
3836 |
|
|
3837 |
|
=back |
3838 |
|
|
3839 |
|
=cut |
3840 |
|
|
3841 |
|
sub WikiNote { |
3842 |
|
# Get the parameter. |
3843 |
|
my ($dataString) = @_; |
3844 |
|
# HTML-escape the text. |
3845 |
|
my $retVal = CGI::escapeHTML($dataString); |
3846 |
|
# Substitute the bulletin board codes. |
3847 |
|
my $italic = WikiTools::ItalicCode(); |
3848 |
|
$retVal =~ s/\[\/?i\]/$italic/g; |
3849 |
|
my $bold = WikiTools::BoldCode(); |
3850 |
|
$retVal =~ s/\[\/?b\]/$bold/g; |
3851 |
|
# Paragraph breaks are the same no matter which Wiki you're using. |
3852 |
|
$retVal =~ s!\[p\]!\n\n!g; |
3853 |
|
# Now we do the links, which are complicated by the need to know two |
3854 |
|
# things: the target URL and the text. |
3855 |
|
while ($retVal =~ /\[link\s+([^\]]+)\]([^\[]+)\[\/link\]/g) { |
3856 |
|
# Replace the matched string with the Wiki markup for links. Note that |
3857 |
|
# $-[0] is the starting position of the match for the entire expression, |
3858 |
|
# and $+[0] is past the ending position. |
3859 |
|
substr $retVal, $-[0], $+[0] - $-[0], WikiTools::LinkMarkup($1, $2); |
3860 |
|
} |
3861 |
|
# Return the result. |
3862 |
|
return $retVal; |
3863 |
|
} |
3864 |
|
|
3865 |
=head3 BeginTran |
=head3 BeginTran |
3866 |
|
|
3867 |
$erdb->BeginTran(); |
$erdb->BeginTran(); |
4144 |
|
|
4145 |
=over 4 |
=over 4 |
4146 |
|
|
4147 |
|
=item indexObject |
4148 |
|
|
4149 |
ERDB XML structure for an index. |
ERDB XML structure for an index. |
4150 |
|
|
4151 |
=item RETURN |
=item RETURN |
4805 |
# be a null string. |
# be a null string. |
4806 |
if ($fileName ne "") { |
if ($fileName ne "") { |
4807 |
# Load the relation from the file. |
# Load the relation from the file. |
4808 |
$retVal = $self->LoadTable($fileName, $relationName, $rebuild); |
$retVal = $self->LoadTable($fileName, $relationName, truncate => $rebuild); |
4809 |
} elsif ($rebuild) { |
} elsif ($rebuild) { |
4810 |
# Here we are rebuilding, but no file exists, so we just re-create the table. |
# Here we are rebuilding, but no file exists, so we just re-create the table. |
4811 |
$self->CreateTable($relationName, 1); |
$self->CreateTable($relationName, 1); |
5552 |
return $retVal; |
return $retVal; |
5553 |
} |
} |
5554 |
|
|
5555 |
=head2 HTML Documentation Utility Methods |
=head2 Documentation Utility Methods |
5556 |
|
|
5557 |
=head3 _ComputeRelationshipSentence |
=head3 _ComputeRelationshipSentence |
5558 |
|
|
5584 |
# Get the parameters. |
# Get the parameters. |
5585 |
my ($relationshipName, $relationshipStructure) = @_; |
my ($relationshipName, $relationshipStructure) = @_; |
5586 |
# Format the relationship sentence. |
# Format the relationship sentence. |
5587 |
my $result = "$relationshipStructure->{from} <b>$relationshipName</b> $relationshipStructure->{to}"; |
my $result = "$relationshipStructure->{from} $relationshipName $relationshipStructure->{to}"; |
5588 |
# Compute the arity. |
# Compute the arity. |
5589 |
my $arityCode = $relationshipStructure->{arity}; |
my $arityCode = $relationshipStructure->{arity}; |
5590 |
my $arity = $ArityTable{$arityCode}; |
my $arity = $ArityTable{$arityCode}; |
5629 |
return $result; |
return $result; |
5630 |
} |
} |
5631 |
|
|
5632 |
|
=head3 _WikiRelationTable |
5633 |
|
|
5634 |
|
Generate the Wiki text for a particular relation. The relation's data will be formatted as a |
5635 |
|
table with three columns-- the field name, the field type, and the field description. |
5636 |
|
|
5637 |
|
This is a static method. |
5638 |
|
|
5639 |
|
=over 4 |
5640 |
|
|
5641 |
|
=item relationName |
5642 |
|
|
5643 |
|
Name of the relation being formatted. |
5644 |
|
|
5645 |
|
=item relationData |
5646 |
|
|
5647 |
|
Hash containing the relation's fields and indexes. |
5648 |
|
|
5649 |
|
=item RETURN |
5650 |
|
|
5651 |
|
Returns a Wiki string that can be used to display the relation name and all of its fields. |
5652 |
|
|
5653 |
|
=back |
5654 |
|
|
5655 |
|
=cut |
5656 |
|
|
5657 |
|
sub _WikiRelationTable { |
5658 |
|
# Get the parameters. |
5659 |
|
my ($relationName, $relationData) = @_; |
5660 |
|
# We'll create a list of lists in here, then call WikiTools::Table to |
5661 |
|
# convert it into a table. |
5662 |
|
my @rows = (); |
5663 |
|
# Push in the header row. |
5664 |
|
push @rows, [qw(Field Type Description)]; |
5665 |
|
# Loop through the fields. |
5666 |
|
for my $field (@{$relationData->{Fields}}) { |
5667 |
|
# Create this field's row. We always have a name and type. |
5668 |
|
my @row = ($field->{name}, $field->{type}); |
5669 |
|
# If we have a description, add it as the third column. |
5670 |
|
if (exists $field->{Notes}) { |
5671 |
|
push @row, WikiNote($field->{Notes}->{content}); |
5672 |
|
} |
5673 |
|
# Push this row onto the table list. |
5674 |
|
push @rows, \@row; |
5675 |
|
} |
5676 |
|
# Store the rows as a Wiki table with a level-4 heading. |
5677 |
|
my $retVal = join("\n\n", WikiTools::Heading(4, "$relationName Table"), |
5678 |
|
WikiTools::Table(@rows)); |
5679 |
|
# Now we show the relation's indexes. These are formatted as another |
5680 |
|
# table. |
5681 |
|
@rows = (); |
5682 |
|
# Push in the header row. |
5683 |
|
push @rows, [qw(Index Unique Fields Notes)]; |
5684 |
|
# Get the index hash. |
5685 |
|
my $indexTable = $relationData->{Indexes}; |
5686 |
|
# Loop through the indexes. For an entity, there is always at least one index. |
5687 |
|
# For a relationship, there are at least two. The upshot is we don't need to |
5688 |
|
# worry about accidentally generating a frivolous table here. |
5689 |
|
for my $indexName (sort keys %$indexTable) { |
5690 |
|
my $indexData = $indexTable->{$indexName}; |
5691 |
|
# Determine whether or not the index is unique. |
5692 |
|
my $unique = ((exists $indexData->{Unique} && $indexData->{Unique} eq "true") ? |
5693 |
|
"yes" : ""); |
5694 |
|
# Get the field list. |
5695 |
|
my $fields = join(', ', @{$indexData->{IndexFields}}); |
5696 |
|
# Get the note text. |
5697 |
|
my $description = ""; |
5698 |
|
if (my $note = $indexData->{Notes}) { |
5699 |
|
$description = WikiNote($note->{content}); |
5700 |
|
} |
5701 |
|
# Format this row. |
5702 |
|
my @row = ($indexName, $unique, $fields, $description); |
5703 |
|
push @rows, \@row; |
5704 |
|
} |
5705 |
|
# Add the index list to the result. |
5706 |
|
$retVal .= "\n\n" . WikiTools::Table(@rows); |
5707 |
|
} |
5708 |
|
|
5709 |
=head3 _ShowRelationTable |
=head3 _ShowRelationTable |
5710 |
|
|
5711 |
Generate the HTML string for a particular relation. The relation's data will be formatted as an HTML |
Generate the HTML string for a particular relation. The relation's data will be formatted as an HTML |
5879 |
return $htmlString; |
return $htmlString; |
5880 |
} |
} |
5881 |
|
|
5882 |
|
=head3 _ObjectNotes |
5883 |
|
|
5884 |
|
my @noteParagraphs = _ObjectNotes($objectData); |
5885 |
|
|
5886 |
|
Return a list of the notes and asides for an entity or relationship in |
5887 |
|
Wiki format. |
5888 |
|
|
5889 |
|
=over 4 |
5890 |
|
|
5891 |
|
=item objectData |
5892 |
|
|
5893 |
|
The metadata for the desired entity or relationship. |
5894 |
|
|
5895 |
|
=item RETURN |
5896 |
|
|
5897 |
|
Returns a list of text paragraphs in Wiki markup form. |
5898 |
|
|
5899 |
|
=back |
5900 |
|
|
5901 |
|
=cut |
5902 |
|
|
5903 |
|
sub _ObjectNotes { |
5904 |
|
# Get the parameters. |
5905 |
|
my ($objectData) = @_; |
5906 |
|
# Declare the return variable. |
5907 |
|
my @retVal; |
5908 |
|
# Loop through the types of notes. |
5909 |
|
for my $noteType (qw(Notes Asides)) { |
5910 |
|
my $text = $objectData->{$noteType}; |
5911 |
|
if ($text) { |
5912 |
|
push @retVal, "", WikiNote($text->{content}); |
5913 |
|
} |
5914 |
|
} |
5915 |
|
# Return the result. |
5916 |
|
return @retVal; |
5917 |
|
} |
5918 |
|
|
5919 |
1; |
1; |