[Bio] / Sprout / ERDB.pm Repository:
ViewVC logotype

Diff of /Sprout/ERDB.pm

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.47, Sun Jun 18 05:14:56 2006 UTC revision 1.72, Mon Oct 16 07:44:41 2006 UTC
# Line 91  Line 91 
91    
92  32-bit signed integer  32-bit signed integer
93    
94    =item counter
95    
96    32-bit unsigned integer
97    
98  =item date  =item date
99    
100  64-bit unsigned integer, representing a PERL date/time value  64-bit unsigned integer, representing a PERL date/time value
# Line 186  Line 190 
190    
191  Name of the field. The field name should contain only letters, digits, and hyphens (C<->),  Name of the field. The field name should contain only letters, digits, and hyphens (C<->),
192  and the first character should be a letter. Most underlying databases are case-insensitive  and the first character should be a letter. Most underlying databases are case-insensitive
193  with the respect to field names, so a best practice is to use lower-case letters only.  with the respect to field names, so a best practice is to use lower-case letters only. Finally,
194    the name C<search-relevance> has special meaning for full-text searches and should not be
195    used as a field name.
196    
197  =item type  =item type
198    
# Line 205  Line 211 
211  entity, the fields without a relation attribute are said to belong to the  entity, the fields without a relation attribute are said to belong to the
212  I<primary relation>. This relation has the same name as the entity itself.  I<primary relation>. This relation has the same name as the entity itself.
213    
214    =item searchable
215    
216    If specified, then the field is a candidate for full-text searching. A single full-text
217    index will be created for each relation with at least one searchable field in it.
218    For best results, this option should only be used for string or text fields.
219    
220  =back  =back
221    
222  =head3 Indexes  =head3 Indexes
# Line 318  Line 330 
330  # "maxLen" is the maximum permissible length of the incoming string data used to populate a field  # "maxLen" is the maximum permissible length of the incoming string data used to populate a field
331  # of the specified type. "dataGen" is PERL string that will be evaluated if no test data generation  # of the specified type. "dataGen" is PERL string that will be evaluated if no test data generation
332  # string is specified in the field definition. "avgLen" is the average byte length for estimating  # string is specified in the field definition. "avgLen" is the average byte length for estimating
333  # record sizes.  # record sizes. "sort" is the key modifier for the sort command.
334  my %TypeTable = ( char =>    { sqlType => 'CHAR(1)',            maxLen => 1,            avgLen =>   1, dataGen => "StringGen('A')" },  my %TypeTable = ( char =>    { sqlType => 'CHAR(1)',            maxLen => 1,            avgLen =>   1, sort => "",  dataGen => "StringGen('A')" },
335                    int =>     { sqlType => 'INTEGER',            maxLen => 20,           avgLen =>   4, dataGen => "IntGen(0, 99999999)" },                    int =>     { sqlType => 'INTEGER',            maxLen => 20,           avgLen =>   4, sort => "n", dataGen => "IntGen(0, 99999999)" },
336                    string =>  { sqlType => 'VARCHAR(255)',       maxLen => 255,          avgLen => 100, dataGen => "StringGen(IntGen(10,250))" },                    counter => { sqlType => 'INTEGER UNSIGNED',   maxLen => 20,           avgLen =>   4, sort => "n", dataGen => "IntGen(0, 99999999)" },
337                    text =>    { sqlType => 'TEXT',               maxLen => 1000000000,   avgLen => 500, dataGen => "StringGen(IntGen(80,1000))" },                    string =>  { sqlType => 'VARCHAR(255)',       maxLen => 255,          avgLen => 100, sort => "",  dataGen => "StringGen(IntGen(10,250))" },
338                    date =>    { sqlType => 'BIGINT',             maxLen => 80,           avgLen =>   8, dataGen => "DateGen(-7, 7, IntGen(0,1400))" },                    text =>    { sqlType => 'TEXT',               maxLen => 1000000000,   avgLen => 500, sort => "",  dataGen => "StringGen(IntGen(80,1000))" },
339                    float =>   { sqlType => 'DOUBLE PRECISION',   maxLen => 40,           avgLen =>   8, dataGen => "FloatGen(0.0, 100.0)" },                    date =>    { sqlType => 'BIGINT',             maxLen => 80,           avgLen =>   8, sort => "n", dataGen => "DateGen(-7, 7, IntGen(0,1400))" },
340                    boolean => { sqlType => 'SMALLINT',           maxLen => 1,            avgLen =>   1, dataGen => "IntGen(0, 1)" },                    float =>   { sqlType => 'DOUBLE PRECISION',   maxLen => 40,           avgLen =>   8, sort => "g", dataGen => "FloatGen(0.0, 100.0)" },
341                      boolean => { sqlType => 'SMALLINT',           maxLen => 1,            avgLen =>   1, sort => "n", dataGen => "IntGen(0, 1)" },
342                   'hash-string' =>                   'hash-string' =>
343                               { sqlType => 'VARCHAR(22)',        maxLen => 22,           avgLen =>  22, dataGen => "SringGen(22)" },                               { sqlType => 'VARCHAR(22)',        maxLen => 22,           avgLen =>  22, sort => "",  dataGen => "SringGen(22)" },
344                   'id-string' =>                   'id-string' =>
345                               { sqlType => 'VARCHAR(25)',        maxLen => 25,           avgLen =>  25, dataGen => "SringGen(22)" },                               { sqlType => 'VARCHAR(25)',        maxLen => 25,           avgLen =>  25, sort => "",  dataGen => "SringGen(22)" },
346                   'key-string' =>                   'key-string' =>
347                               { sqlType => 'VARCHAR(40)',        maxLen => 40,           avgLen =>  10, dataGen => "StringGen(IntGen(10,40))" },                               { sqlType => 'VARCHAR(40)',        maxLen => 40,           avgLen =>  10, sort => "",  dataGen => "StringGen(IntGen(10,40))" },
348                   'name-string' =>                   'name-string' =>
349                               { sqlType => 'VARCHAR(80)',        maxLen => 80,           avgLen =>  40, dataGen => "StringGen(IntGen(10,80))" },                               { sqlType => 'VARCHAR(80)',        maxLen => 80,           avgLen =>  40, sort => "",  dataGen => "StringGen(IntGen(10,80))" },
350                   'medium-string' =>                   'medium-string' =>
351                               { sqlType => 'VARCHAR(160)',       maxLen => 160,          avgLen =>  40, dataGen => "StringGen(IntGen(10,160))" },                               { sqlType => 'VARCHAR(160)',       maxLen => 160,          avgLen =>  40, sort => "",  dataGen => "StringGen(IntGen(10,160))" },
352                  );                  );
353    
354  # Table translating arities into natural language.  # Table translating arities into natural language.
# Line 684  Line 697 
697      Trace("Creating table $relationName: $fieldThing") if T(2);      Trace("Creating table $relationName: $fieldThing") if T(2);
698      $dbh->create_table(tbl => $relationName, flds => $fieldThing, estimates => $estimation);      $dbh->create_table(tbl => $relationName, flds => $fieldThing, estimates => $estimation);
699      Trace("Relation $relationName created in database.") if T(2);      Trace("Relation $relationName created in database.") if T(2);
700      # If we want to build the indexes, we do it here.      # If we want to build the indexes, we do it here. Note that the full-text search
701        # index will not be built until the table has been loaded.
702      if ($indexFlag) {      if ($indexFlag) {
703          $self->CreateIndex($relationName);          $self->CreateIndex($relationName);
704      }      }
# Line 844  Line 858 
858          my @fieldList = _FixNames(@{$indexData->{IndexFields}});          my @fieldList = _FixNames(@{$indexData->{IndexFields}});
859          my $flds = join(', ', @fieldList);          my $flds = join(', ', @fieldList);
860          # Get the index's uniqueness flag.          # Get the index's uniqueness flag.
861          my $unique = (exists $indexData->{Unique} ? $indexData->{Unique} : 'false');          my $unique = (exists $indexData->{Unique} ? 'unique' : undef);
862          # Create the index.          # Create the index.
863          my $rv = $dbh->create_index(idx => $indexName, tbl => $relationName,          my $rv = $dbh->create_index(idx => $indexName, tbl => $relationName,
864                                      flds => $flds, unique => $unique);                                      flds => $flds, kind => $unique);
865          if ($rv) {          if ($rv) {
866              Trace("Index created: $indexName for $relationName ($flds)") if T(1);              Trace("Index created: $indexName for $relationName ($flds)") if T(1);
867          } else {          } else {
# Line 1094  Line 1108 
1108      return $retVal;      return $retVal;
1109  }  }
1110    
1111    =head3 Search
1112    
1113    C<< my $query = $erdb->Search($searchExpression, $idx, \@objectNames, $filterClause, \@params); >>
1114    
1115    Perform a full text search with filtering. The search will be against a specified object
1116    in the object name list. That object will get an extra field containing the search
1117    relevance. Note that except for the search expression, the parameters of this method are
1118    the same as those for L</Get> and follow the same rules.
1119    
1120    =over 4
1121    
1122    =item searchExpression
1123    
1124    Boolean search expression for the text fields of the target object.
1125    
1126    =item idx
1127    
1128    Index in the I<$objectNames> list of the table to be searched in full-text mode.
1129    
1130    =item objectNames
1131    
1132    List containing the names of the entity and relationship objects to be retrieved.
1133    
1134    =item filterClause
1135    
1136    WHERE clause (without the WHERE) to be used to filter and sort the query. The WHERE clause can
1137    be parameterized with parameter markers (C<?>). Each field used in the WHERE clause must be
1138    specified in the standard form B<I<objectName>(I<fieldName>)>. Any parameters specified
1139    in the filter clause should be added to the parameter list as additional parameters. The
1140    fields in a filter clause can come from primary entity relations, relationship relations,
1141    or secondary entity relations; however, all of the entities and relationships involved must
1142    be included in the list of object names.
1143    
1144    =item params
1145    
1146    Reference to a list of parameter values to be substituted into the filter clause.
1147    
1148    =item RETURN
1149    
1150    Returns a query object for the specified search.
1151    
1152    =back
1153    
1154    =cut
1155    
1156    sub Search {
1157        # Get the parameters.
1158        my ($self, $searchExpression, $idx, $objectNames, $filterClause, $params) = @_;
1159        # Declare the return variable.
1160        my $retVal;
1161        # Create a safety copy of the parameter list. Note we have to be careful to insure
1162        # a parameter list exists before we copy it.
1163        my @myParams = ();
1164        if (defined $params) {
1165            @myParams = @{$params};
1166        }
1167        # Get the first object's structure so we have access to the searchable fields.
1168        my $object1Name = $objectNames->[$idx];
1169        my $object1Structure = $self->_GetStructure($object1Name);
1170        # Get the field list.
1171        if (! exists $object1Structure->{searchFields}) {
1172            Confess("No searchable index for $object1Name.");
1173        } else {
1174            # Get the field list.
1175            my @fields = @{$object1Structure->{searchFields}};
1176            # Clean the search expression.
1177            my $actualKeywords = $self->CleanKeywords($searchExpression);
1178            Trace("Actual keywords for search are\n$actualKeywords") if T(3);
1179            # We need two match expressions, one for the filter clause and one in the
1180            # query itself. Both will use a parameter mark, so we need to push the
1181            # search expression onto the front of the parameter list twice.
1182            unshift @myParams, $actualKeywords, $actualKeywords;
1183            # Build the match expression.
1184            my @matchFilterFields = map { "$object1Name." . _FixName($_) } @fields;
1185            my $matchClause = "MATCH (" . join(", ", @matchFilterFields) . ") AGAINST (? IN BOOLEAN MODE)";
1186            # Process the SQL stuff.
1187            my ($suffix, $mappedNameListRef, $mappedNameHashRef) =
1188                $self->_SetupSQL($objectNames, $filterClause, $matchClause);
1189            # Create the query. Note that the match clause is inserted at the front of
1190            # the select fields.
1191            my $command = "SELECT DISTINCT $matchClause, " . join(".*, ", @{$mappedNameListRef}) .
1192                ".* $suffix";
1193            my $sth = $self->_GetStatementHandle($command, \@myParams);
1194            # Now we create the relation map, which enables DBQuery to determine the order, name
1195            # and mapped name for each object in the query.
1196            my @relationMap = _RelationMap($mappedNameHashRef, $mappedNameListRef);
1197            # Return the statement object.
1198            $retVal = DBQuery::_new($self, $sth, \@relationMap, $object1Name);
1199        }
1200        return $retVal;
1201    }
1202    
1203  =head3 GetFlat  =head3 GetFlat
1204    
1205  C<< my @list = $erdb->GetFlat(\@objectNames, $filterClause, \@parameterList, $field); >>  C<< my @list = $erdb->GetFlat(\@objectNames, $filterClause, \@parameterList, $field); >>
# Line 1315  Line 1421 
1421      return $retVal;      return $retVal;
1422  }  }
1423    
1424    =head3 SortNeeded
1425    
1426    C<< my $parms = $erdb->SortNeeded($relationName); >>
1427    
1428    Return the pipe command for the sort that should be applied to the specified
1429    relation when creating the load file.
1430    
1431    For example, if the load file should be sorted ascending by the first
1432    field, this method would return
1433    
1434        sort -k1 -t"\t"
1435    
1436    If the first field is numeric, the method would return
1437    
1438        sort -k1n -t"\t"
1439    
1440    Unfortunately, due to a bug in the C<sort> command, we cannot eliminate duplicate
1441    keys using a sort.
1442    
1443    =over 4
1444    
1445    =item relationName
1446    
1447    Name of the relation to be examined.
1448    
1449    =item
1450    
1451    Returns the sort command to use for sorting the relation, suitable for piping.
1452    
1453    =back
1454    
1455    =cut
1456    #: Return Type $;
1457    sub SortNeeded {
1458        # Get the parameters.
1459        my ($self, $relationName) = @_;
1460        # Declare a descriptor to hold the names of the key fields.
1461        my @keyNames = ();
1462        # Get the relation structure.
1463        my $relationData = $self->_FindRelation($relationName);
1464        # Find out if the relation is a primary entity relation,
1465        # a relationship relation, or a secondary entity relation.
1466        my $entityTable = $self->{_metaData}->{Entities};
1467        my $relationshipTable = $self->{_metaData}->{Relationships};
1468        if (exists $entityTable->{$relationName}) {
1469            # Here we have a primary entity relation.
1470            push @keyNames, "id";
1471        } elsif (exists $relationshipTable->{$relationName}) {
1472            # Here we have a relationship. We sort using the FROM index.
1473            my $relationshipData = $relationshipTable->{$relationName};
1474            my $index = $relationData->{Indexes}->{"idx${relationName}From"};
1475            push @keyNames, @{$index->{IndexFields}};
1476        } else {
1477            # Here we have a secondary entity relation, so we have a sort on the ID field.
1478            push @keyNames, "id";
1479        }
1480        # Now we parse the key names into sort parameters. First, we prime the return
1481        # string.
1482        my $retVal = "sort -t\"\t\" ";
1483        # Get the relation's field list.
1484        my @fields = @{$relationData->{Fields}};
1485        # Loop through the keys.
1486        for my $keyData (@keyNames) {
1487            # Get the key and the ordering.
1488            my ($keyName, $ordering);
1489            if ($keyData =~ /^([^ ]+) DESC/) {
1490                ($keyName, $ordering) = ($1, "descending");
1491            } else {
1492                ($keyName, $ordering) = ($keyData, "ascending");
1493            }
1494            # Find the key's position and type.
1495            my $fieldSpec;
1496            for (my $i = 0; $i <= $#fields && ! $fieldSpec; $i++) {
1497                my $thisField = $fields[$i];
1498                if ($thisField->{name} eq $keyName) {
1499                    # Get the sort modifier for this field type. The modifier
1500                    # decides whether we're using a character, numeric, or
1501                    # floating-point sort.
1502                    my $modifier = $TypeTable{$thisField->{type}}->{sort};
1503                    # If the index is descending for this field, denote we want
1504                    # to reverse the sort order on this field.
1505                    if ($ordering eq 'descending') {
1506                        $modifier .= "r";
1507                    }
1508                    # Store the position and modifier into the field spec, which
1509                    # will stop the inner loop. Note that the field number is
1510                    # 1-based in the sort command, so we have to increment the
1511                    # index.
1512                    $fieldSpec = ($i + 1) . $modifier;
1513                }
1514            }
1515            # Add this field to the sort command.
1516            $retVal .= " -k$fieldSpec";
1517        }
1518        # Return the result.
1519        return $retVal;
1520    }
1521    
1522  =head3 GetList  =head3 GetList
1523    
1524  C<< my @dbObjects = $erdb->GetList(\@objectNames, $filterClause, \@params); >>  C<< my @dbObjects = $erdb->GetList(\@objectNames, $filterClause, \@params); >>
# Line 1431  Line 1635 
1635  sub GetCount {  sub GetCount {
1636      # Get the parameters.      # Get the parameters.
1637      my ($self, $objectNames, $filter, $params) = @_;      my ($self, $objectNames, $filter, $params) = @_;
1638        # Insure the params argument is an array reference if the caller left it off.
1639        if (! defined($params)) {
1640            $params = [];
1641        }
1642      # Declare the return variable.      # Declare the return variable.
1643      my $retVal;      my $retVal;
1644      # Find out if we're counting an entity or a relationship.      # Find out if we're counting an entity or a relationship.
# Line 1544  Line 1752 
1752      }      }
1753  }  }
1754    
1755    =head3 InsertValue
1756    
1757    C<< $erdb->InsertValue($entityID, $fieldName, $value); >>
1758    
1759    This method will insert a new value into the database. The value must be one
1760    associated with a secondary relation, since primary values cannot be inserted:
1761    they occur exactly once. Secondary values, on the other hand, can be missing
1762    or multiply-occurring.
1763    
1764    =over 4
1765    
1766    =item entityID
1767    
1768    ID of the object that is to receive the new value.
1769    
1770    =item fieldName
1771    
1772    Field name for the new value-- this includes the entity name, since
1773    field names are of the format I<objectName>C<(>I<fieldName>C<)>.
1774    
1775    =item value
1776    
1777    New value to be put in the field.
1778    
1779    =back
1780    
1781    =cut
1782    
1783    sub InsertValue {
1784        # Get the parameters.
1785        my ($self, $entityID, $fieldName, $value) = @_;
1786        # Parse the entity name and the real field name.
1787        if ($fieldName =~ /^([^(]+)\(([^)]+)\)/) {
1788            my $entityName = $1;
1789            my $fieldTitle = $2;
1790            # Get its descriptor.
1791            if (!$self->IsEntity($entityName)) {
1792                Confess("$entityName is not a valid entity.");
1793            } else {
1794                my $entityData = $self->{_metaData}->{Entities}->{$entityName};
1795                # Find the relation containing this field.
1796                my $fieldHash = $entityData->{Fields};
1797                if (! exists $fieldHash->{$fieldTitle}) {
1798                    Confess("$fieldTitle not found in $entityName.");
1799                } else {
1800                    my $relation = $fieldHash->{$fieldTitle}->{relation};
1801                    if ($relation eq $entityName) {
1802                        Confess("Cannot do InsertValue on primary field $fieldTitle of $entityName.");
1803                    } else {
1804                        # Now we can create an INSERT statement.
1805                        my $dbh = $self->{_dbh};
1806                        my $fixedName = _FixName($fieldTitle);
1807                        my $statement = "INSERT INTO $relation (id, $fixedName) VALUES(?, ?)";
1808                        # Execute the command.
1809                        $dbh->SQL($statement, 0, $entityID, $value);
1810                    }
1811                }
1812            }
1813        } else {
1814            Confess("$fieldName is not a valid field name.");
1815        }
1816    }
1817    
1818  =head3 InsertObject  =head3 InsertObject
1819    
1820  C<< my $ok = $erdb->InsertObject($objectType, \%fieldHash); >>  C<< my $ok = $erdb->InsertObject($objectType, \%fieldHash); >>
# Line 1560  Line 1831 
1831  The next statement inserts a C<HasProperty> relationship between feature C<fig|158879.1.peg.1> and  The next statement inserts a C<HasProperty> relationship between feature C<fig|158879.1.peg.1> and
1832  property C<4> with an evidence URL of C<http://seedu.uchicago.edu/query.cgi?article_id=142>.  property C<4> with an evidence URL of C<http://seedu.uchicago.edu/query.cgi?article_id=142>.
1833    
1834  C<< $erdb->InsertObject('HasProperty', { 'from-link' => 'fig|158879.1.peg.1', 'to-link' => 4, evidence = 'http://seedu.uchicago.edu/query.cgi?article_id=142'}); >>  C<< $erdb->InsertObject('HasProperty', { 'from-link' => 'fig|158879.1.peg.1', 'to-link' => 4, evidence => 'http://seedu.uchicago.edu/query.cgi?article_id=142'}); >>
1835    
1836  =over 4  =over 4
1837    
# Line 1760  Line 2031 
2031          my $size = -s $fileName;          my $size = -s $fileName;
2032          Trace("$size bytes loaded into $relationName.") if T(2);          Trace("$size bytes loaded into $relationName.") if T(2);
2033          # If we're rebuilding, we need to create the table indexes.          # If we're rebuilding, we need to create the table indexes.
2034          if ($truncateFlag && ! $dbh->{_preIndex}) {          if ($truncateFlag) {
2035                # Indexes are created here for PostGres. For PostGres, indexes are
2036                # best built at the end. For MySQL, the reverse is true.
2037                if (! $dbh->{_preIndex}) {
2038              eval {              eval {
2039                  $self->CreateIndex($relationName);                  $self->CreateIndex($relationName);
2040              };              };
# Line 1768  Line 2042 
2042                  $retVal->AddMessage($@);                  $retVal->AddMessage($@);
2043              }              }
2044          }          }
2045                # The full-text index (if any) is always built last, even for MySQL.
2046                # First we need to see if this table has a full-text index. Only
2047                # primary relations are allowed that privilege.
2048                if ($self->_IsPrimary($relationName)) {
2049                    # Get the relation's entity/relationship structure.
2050                    my $structure = $self->_GetStructure($relationName);
2051                    # Check for a searchable fields list.
2052                    if (exists $structure->{searchFields}) {
2053                        # Here we know that we need to create a full-text search index.
2054                        # Get an SQL-formatted field name list.
2055                        my $fields = join(", ", $self->_FixNames(@{$structure->{searchFields}}));
2056                        # Create the index.
2057                        $dbh->create_index(tbl => $relationName, idx => "search_idx_$relationName",
2058                                           flds => $fields, kind => 'fulltext');
2059                    }
2060                }
2061            }
2062      }      }
2063      # Analyze the table to improve performance.      # Analyze the table to improve performance.
2064        Trace("Analyzing and compacting $relationName.") if T(3);
2065      $dbh->vacuum_it($relationName);      $dbh->vacuum_it($relationName);
2066        Trace("$relationName load completed.") if T(3);
2067      # Return the statistics.      # Return the statistics.
2068      return $retVal;      return $retVal;
2069  }  }
# Line 1869  Line 2162 
2162      return $retVal;      return $retVal;
2163  }  }
2164    
2165    =head3 GetChoices
2166    
2167    C<< my @values = $erdb->GetChoices($entityName, $fieldName); >>
2168    
2169    Return a list of all the values for the specified field that are represented in the
2170    specified entity.
2171    
2172    Note that if the field is not indexed, then this will be a very slow operation.
2173    
2174    =over 4
2175    
2176    =item entityName
2177    
2178    Name of an entity in the database.
2179    
2180    =item fieldName
2181    
2182    Name of a field belonging to the entity. This is a raw field name without
2183    the standard parenthesized notation used in most calls.
2184    
2185    =item RETURN
2186    
2187    Returns a list of the distinct values for the specified field in the database.
2188    
2189    =back
2190    
2191    =cut
2192    
2193    sub GetChoices {
2194        # Get the parameters.
2195        my ($self, $entityName, $fieldName) = @_;
2196        # Declare the return variable.
2197        my @retVal;
2198        # Get the entity data structure.
2199        my $entityData = $self->_GetStructure($entityName);
2200        # Get the field.
2201        my $fieldHash = $entityData->{Fields};
2202        if (! exists $fieldHash->{$fieldName}) {
2203            Confess("$fieldName not found in $entityName.");
2204        } else {
2205            # Get the name of the relation containing the field.
2206            my $relation = $fieldHash->{$fieldName}->{relation};
2207            # Fix up the field name.
2208            my $realName = _FixName($fieldName);
2209            # Get the database handle.
2210            my $dbh = $self->{_dbh};
2211            # Query the database.
2212            my $results = $dbh->SQL("SELECT DISTINCT $realName FROM $relation");
2213            # Clean the results. They are stored as a list of lists, and we just want the one list.
2214            @retVal = sort map { $_->[0] } @{$results};
2215        }
2216        # Return the result.
2217        return @retVal;
2218    }
2219    
2220  =head3 GetEntityValues  =head3 GetEntityValues
2221    
2222  C<< my @values = $erdb->GetEntityValues($entityType, $ID, \@fields); >>  C<< my @values = $erdb->GetEntityValues($entityType, $ID, \@fields); >>
2223    
2224  Return a list of values from a specified entity instance.  Return a list of values from a specified entity instance. If the entity instance
2225    does not exist, an empty list is returned.
2226    
2227  =over 4  =over 4
2228    
# Line 2001  Line 2350 
2350          push @retVal, \@rowData;          push @retVal, \@rowData;
2351          $fetched++;          $fetched++;
2352      }      }
2353        Trace("$fetched rows returned in GetAll.") if T(SQL => 4);
2354      # Return the resulting list.      # Return the resulting list.
2355      return @retVal;      return @retVal;
2356  }  }
2357    
2358    =head3 Exists
2359    
2360    C<< my $found = $sprout->Exists($entityName, $entityID); >>
2361    
2362    Return TRUE if an entity exists, else FALSE.
2363    
2364    =over 4
2365    
2366    =item entityName
2367    
2368    Name of the entity type (e.g. C<Feature>) relevant to the existence check.
2369    
2370    =item entityID
2371    
2372    ID of the entity instance whose existence is to be checked.
2373    
2374    =item RETURN
2375    
2376    Returns TRUE if the entity instance exists, else FALSE.
2377    
2378    =back
2379    
2380    =cut
2381    #: Return Type $;
2382    sub Exists {
2383        # Get the parameters.
2384        my ($self, $entityName, $entityID) = @_;
2385        # Check for the entity instance.
2386        Trace("Checking existence of $entityName with ID=$entityID.") if T(4);
2387        my $testInstance = $self->GetEntity($entityName, $entityID);
2388        # Return an existence indicator.
2389        my $retVal = ($testInstance ? 1 : 0);
2390        return $retVal;
2391    }
2392    
2393  =head3 EstimateRowSize  =head3 EstimateRowSize
2394    
2395  C<< my $rowSize = $erdb->EstimateRowSize($relName); >>  C<< my $rowSize = $erdb->EstimateRowSize($relName); >>
# Line 2072  Line 2457 
2457      return $objectData->{Fields};      return $objectData->{Fields};
2458  }  }
2459    
2460    =head2 Data Mining Methods
2461    
2462  =head3 GetUsefulCrossValues  =head3 GetUsefulCrossValues
2463    
2464  C<< my @attrNames = $sprout->GetUsefulCrossValues($sourceEntity, $relationship); >>  C<< my @attrNames = $sprout->GetUsefulCrossValues($sourceEntity, $relationship); >>
# Line 2133  Line 2520 
2520      return @retVal;      return @retVal;
2521  }  }
2522    
2523    =head3 FindColumn
2524    
2525    C<< my $colIndex = ERDB::FindColumn($headerLine, $columnIdentifier); >>
2526    
2527    Return the location a desired column in a data mining header line. The data
2528    mining header line is a tab-separated list of column names. The column
2529    identifier is either the numerical index of a column or the actual column
2530    name.
2531    
2532    =over 4
2533    
2534    =item headerLine
2535    
2536    The header line from a data mining command, which consists of a tab-separated
2537    list of column names.
2538    
2539    =item columnIdentifier
2540    
2541    Either the ordinal number of the desired column (1-based), or the name of the
2542    desired column.
2543    
2544    =item RETURN
2545    
2546    Returns the array index (0-based) of the desired column.
2547    
2548    =back
2549    
2550    =cut
2551    
2552    sub FindColumn {
2553        # Get the parameters.
2554        my ($headerLine, $columnIdentifier) = @_;
2555        # Declare the return variable.
2556        my $retVal;
2557        # Split the header line into column names.
2558        my @headers = ParseColumns($headerLine);
2559        # Determine whether we have a number or a name.
2560        if ($columnIdentifier =~ /^\d+$/) {
2561            # Here we have a number. Subtract 1 and validate the result.
2562            $retVal = $columnIdentifier - 1;
2563            if ($retVal < 0 || $retVal > $#headers) {
2564                Confess("Invalid column identifer \"$columnIdentifier\": value out of range.");
2565            }
2566        } else {
2567            # Here we have a name. We need to find it in the list.
2568            for (my $i = 0; $i <= $#headers && ! defined($retVal); $i++) {
2569                if ($headers[$i] eq $columnIdentifier) {
2570                    $retVal = $i;
2571                }
2572            }
2573            if (! defined($retVal)) {
2574                Confess("Invalid column identifier \"$columnIdentifier\": value not found.");
2575            }
2576        }
2577        # Return the result.
2578        return $retVal;
2579    }
2580    
2581    =head3 ParseColumns
2582    
2583    C<< my @columns = ERDB::ParseColumns($line); >>
2584    
2585    Convert the specified data line to a list of columns.
2586    
2587    =over 4
2588    
2589    =item line
2590    
2591    A data mining input, consisting of a tab-separated list of columns terminated by a
2592    new-line.
2593    
2594    =item RETURN
2595    
2596    Returns a list consisting of the column values.
2597    
2598    =back
2599    
2600    =cut
2601    
2602    sub ParseColumns {
2603        # Get the parameters.
2604        my ($line) = @_;
2605        # Chop off the line-end.
2606        chomp $line;
2607        # Split it into a list.
2608        my @retVal = split(/\t/, $line);
2609        # Return the result.
2610        return @retVal;
2611    }
2612    
2613    =head2 Virtual Methods
2614    
2615    =head3 CleanKeywords
2616    
2617    C<< my $cleanedString = $erdb->CleanKeywords($searchExpression); >>
2618    
2619    Clean up a search expression or keyword list. This is a virtual method that may
2620    be overridden by the subclass. The base-class method removes extra spaces
2621    and converts everything to lower case.
2622    
2623    =over 4
2624    
2625    =item searchExpression
2626    
2627    Search expression or keyword list to clean. Note that a search expression may
2628    contain boolean operators which need to be preserved. This includes leading
2629    minus signs.
2630    
2631    =item RETURN
2632    
2633    Cleaned expression or keyword list.
2634    
2635    =back
2636    
2637    =cut
2638    
2639    sub CleanKeywords {
2640        # Get the parameters.
2641        my ($self, $searchExpression) = @_;
2642        # Lower-case the expression and copy it into the return variable. Note that we insure we
2643        # don't accidentally end up with an undefined value.
2644        my $retVal = lc($searchExpression || "");
2645        # Remove extra spaces.
2646        $retVal =~ s/\s+/ /g;
2647        $retVal =~ s/(^\s+)|(\s+$)//g;
2648        # Return the result.
2649        return $retVal;
2650    }
2651    
2652  =head2 Internal Utility Methods  =head2 Internal Utility Methods
2653    
2654  =head3 SetupSQL  =head3 _RelationMap
2655    
2656    C<< my @relationMap = _RelationMap($mappedNameHashRef, $mappedNameListRef); >>
2657    
2658    Create the relation map for an SQL query. The relation map is used by B<DBObject>
2659    to determine how to interpret the results of the query.
2660    
2661    =over 4
2662    
2663    =item mappedNameHashRef
2664    
2665    Reference to a hash that maps modified object names to real object names.
2666    
2667    =item mappedNameListRef
2668    
2669    Reference to a list of modified object names in the order they appear in the
2670    SELECT list.
2671    
2672    =item RETURN
2673    
2674    Returns a list of 2-tuples. Each tuple consists of an object name as used in the
2675    query followed by the actual name of that object. This enables the B<DBObject> to
2676    determine the order of the tables in the query and which object name belongs to each
2677    mapped object name. Most of the time these two values are the same; however, if a
2678    relation occurs twice in the query, the relation name in the field list and WHERE
2679    clause will use a mapped name (generally the actual relation name with a numeric
2680    suffix) that does not match the actual relation name.
2681    
2682    =back
2683    
2684    =cut
2685    
2686    sub _RelationMap {
2687        # Get the parameters.
2688        my ($mappedNameHashRef, $mappedNameListRef) = @_;
2689        # Declare the return variable.
2690        my @retVal = ();
2691        # Build the map.
2692        for my $mappedName (@{$mappedNameListRef}) {
2693            push @retVal, [$mappedName, $mappedNameHashRef->{$mappedName}];
2694        }
2695        # Return it.
2696        return @retVal;
2697    }
2698    
2699    
2700    =head3 _SetupSQL
2701    
2702  Process a list of object names and a filter clause so that they can be used to  Process a list of object names and a filter clause so that they can be used to
2703  build an SQL statement. This method takes in a reference to a list of object names  build an SQL statement. This method takes in a reference to a list of object names
# Line 2155  Line 2717 
2717  A string containing the WHERE clause for the query (without the C<WHERE>) and also  A string containing the WHERE clause for the query (without the C<WHERE>) and also
2718  optionally the C<ORDER BY> and C<LIMIT> clauses.  optionally the C<ORDER BY> and C<LIMIT> clauses.
2719    
2720    =item matchClause
2721    
2722    An optional full-text search clause. If specified, it will be inserted at the
2723    front of the WHERE clause. It should already be SQL-formatted; that is, the
2724    field names should be in the form I<table>C<.>I<fieldName>.
2725    
2726  =item RETURN  =item RETURN
2727    
2728  Returns a three-element list. The first element is the SQL statement suffix, beginning  Returns a three-element list. The first element is the SQL statement suffix, beginning
# Line 2167  Line 2735 
2735  =cut  =cut
2736    
2737  sub _SetupSQL {  sub _SetupSQL {
2738      my ($self, $objectNames, $filterClause) = @_;      my ($self, $objectNames, $filterClause, $matchClause) = @_;
2739      # Adjust the list of object names to account for multiple occurrences of the      # Adjust the list of object names to account for multiple occurrences of the
2740      # same object. We start with a hash table keyed on object name that will      # same object. We start with a hash table keyed on object name that will
2741      # return the object suffix. The first time an object is encountered it will      # return the object suffix. The first time an object is encountered it will
# Line 2216  Line 2784 
2784      # FROM name1, name2, ... nameN      # FROM name1, name2, ... nameN
2785      #      #
2786      my $suffix = "FROM " . join(', ', @fromList);      my $suffix = "FROM " . join(', ', @fromList);
2787        # Now for the WHERE. First, we need a place for the filter string.
2788        my $filterString = "";
2789        # We will also keep a list of conditions to add to the WHERE clause in order to link
2790        # entities and relationships as well as primary relations to secondary ones.
2791        my @joinWhere = ();
2792      # Check for a filter clause.      # Check for a filter clause.
2793      if ($filterClause) {      if ($filterClause) {
2794          # Here we have one, so we convert its field names and add it to the query. First,          # Here we have one, so we convert its field names and add it to the query. First,
2795          # We create a copy of the filter string we can work with.          # We create a copy of the filter string we can work with.
2796          my $filterString = $filterClause;          $filterString = $filterClause;
2797          # Next, we sort the object names by length. This helps protect us from finding          # Next, we sort the object names by length. This helps protect us from finding
2798          # object names inside other object names when we're doing our search and replace.          # object names inside other object names when we're doing our search and replace.
2799          my @sortedNames = sort { length($b) - length($a) } @mappedNameList;          my @sortedNames = sort { length($b) - length($a) } @mappedNameList;
         # We will also keep a list of conditions to add to the WHERE clause in order to link  
         # entities and relationships as well as primary relations to secondary ones.  
         my @joinWhere = ();  
2800          # The final preparatory step is to create a hash table of relation names. The          # The final preparatory step is to create a hash table of relation names. The
2801          # table begins with the relation names already in the SELECT command. We may          # table begins with the relation names already in the SELECT command. We may
2802          # need to add relations later if there is filtering on a field in a secondary          # need to add relations later if there is filtering on a field in a secondary
# Line 2294  Line 2864 
2864                  }                  }
2865              }              }
2866          }          }
2867        }
2868          # The next step is to join the objects together. We only need to do this if there          # The next step is to join the objects together. We only need to do this if there
2869          # is more than one object in the object list. We start with the first object and          # is more than one object in the object list. We start with the first object and
2870          # run through the objects after it. Note also that we make a safety copy of the          # run through the objects after it. Note also that we make a safety copy of the
2871          # list before running through it.      # list before running through it, because we shift off the first object before
2872        # processing the rest.
2873          my @mappedObjectList = @mappedNameList;          my @mappedObjectList = @mappedNameList;
2874          my $lastMappedObject = shift @mappedObjectList;          my $lastMappedObject = shift @mappedObjectList;
2875          # Get the join table.          # Get the join table.
# Line 2326  Line 2898 
2898          # here is we want the filter clause to be empty if there's no WHERE filter.          # here is we want the filter clause to be empty if there's no WHERE filter.
2899          # We'll put the ORDER BY / LIMIT clauses in the following variable.          # We'll put the ORDER BY / LIMIT clauses in the following variable.
2900          my $orderClause = "";          my $orderClause = "";
2901        # This is only necessary if we have a filter string in which the ORDER BY
2902        # and LIMIT clauses can live.
2903        if ($filterString) {
2904          # Locate the ORDER BY or LIMIT verbs (if any). We use a non-greedy          # Locate the ORDER BY or LIMIT verbs (if any). We use a non-greedy
2905          # operator so that we find the first occurrence of either verb.          # operator so that we find the first occurrence of either verb.
2906          if ($filterString =~ m/^(.*?)\s*(ORDER BY|LIMIT)/g) {          if ($filterString =~ m/^(.*?)\s*(ORDER BY|LIMIT)/g) {
# Line 2334  Line 2909 
2909              $orderClause = $2 . substr($filterString, $pos);              $orderClause = $2 . substr($filterString, $pos);
2910              $filterString = $1;              $filterString = $1;
2911          }          }
2912          # Add the filter and the join clauses (if any) to the SELECT command.      }
2913        # All the things that are supposed to be in the WHERE clause of the
2914        # SELECT command need to be put into @joinWhere so we can string them
2915        # together. We begin with the match clause. This is important,
2916        # because the match clause's parameter mark must precede any parameter
2917        # marks in the filter string.
2918        if ($matchClause) {
2919            push @joinWhere, $matchClause;
2920        }
2921        # Add the filter string. We put it in parentheses to avoid operator
2922        # precedence problems with the match clause or the joins.
2923          if ($filterString) {          if ($filterString) {
2924              Trace("Filter string is \"$filterString\".") if T(4);              Trace("Filter string is \"$filterString\".") if T(4);
2925              push @joinWhere, "($filterString)";              push @joinWhere, "($filterString)";
2926          }          }
2927        # String it all together into a big filter clause.
2928          if (@joinWhere) {          if (@joinWhere) {
2929              $suffix .= " WHERE " . join(' AND ', @joinWhere);              $suffix .= " WHERE " . join(' AND ', @joinWhere);
2930          }          }
2931          # Add the sort or limit clause (if any) to the SELECT command.      # Add the sort or limit clause (if any).
2932          if ($orderClause) {          if ($orderClause) {
2933              $suffix .= " $orderClause";              $suffix .= " $orderClause";
2934          }          }
     }  
2935      # Return the suffix, the mapped name list, and the mapped name hash.      # Return the suffix, the mapped name list, and the mapped name hash.
2936      return ($suffix, \@mappedNameList, \%mappedNameHash);      return ($suffix, \@mappedNameList, \%mappedNameHash);
2937  }  }
2938    
2939  =head3 GetStatementHandle  =head3 _GetStatementHandle
2940    
2941  This method will prepare and execute an SQL query, returning the statement handle.  This method will prepare and execute an SQL query, returning the statement handle.
2942  The main reason for doing this here is so that everybody who does SQL queries gets  The main reason for doing this here is so that everybody who does SQL queries gets
# Line 2394  Line 2979 
2979      return $sth;      return $sth;
2980  }  }
2981    
2982  =head3 GetLoadStats  =head3 _GetLoadStats
2983    
2984  Return a blank statistics object for use by the load methods.  Return a blank statistics object for use by the load methods.
2985    
# Line 2406  Line 2991 
2991      return Stats->new();      return Stats->new();
2992  }  }
2993    
2994  =head3 GenerateFields  =head3 _GenerateFields
2995    
2996  Generate field values from a field structure and store in a specified table. The field names  Generate field values from a field structure and store in a specified table. The field names
2997  are first sorted by pass count, certain pre-defined fields are removed from the list, and  are first sorted by pass count, certain pre-defined fields are removed from the list, and
# Line 2480  Line 3065 
3065      }      }
3066  }  }
3067    
3068  =head3 DumpRelation  =head3 _DumpRelation
3069    
3070  Dump the specified relation's to the specified output file in tab-delimited format.  Dump the specified relation's to the specified output file in tab-delimited format.
3071    
# Line 2530  Line 3115 
3115      close DTXOUT;      close DTXOUT;
3116  }  }
3117    
3118  =head3 GetStructure  =head3 _GetStructure
3119    
3120  Get the data structure for a specified entity or relationship.  Get the data structure for a specified entity or relationship.
3121    
# Line 2569  Line 3154 
3154      return $retVal;      return $retVal;
3155  }  }
3156    
3157  =head3 GetRelationTable  
3158    
3159    =head3 _GetRelationTable
3160    
3161  Get the list of relations for a specified entity or relationship.  Get the list of relations for a specified entity or relationship.
3162    
# Line 2598  Line 3185 
3185      return $objectData->{Relations};      return $objectData->{Relations};
3186  }  }
3187    
3188  =head3 ValidateFieldNames  =head3 _ValidateFieldNames
3189    
3190  Determine whether or not the field names are valid. A description of the problems with the names  Determine whether or not the field names are valid. A description of the problems with the names
3191  will be written to the standard error output. If there is an error, this method will abort. This is  will be written to the standard error output. If there is an error, this method will abort. This is
# Line 2653  Line 3240 
3240      }      }
3241  }  }
3242    
3243  =head3 LoadRelation  =head3 _LoadRelation
3244    
3245  Load a relation from the data in a tab-delimited disk file. The load will only take place if a disk  Load a relation from the data in a tab-delimited disk file. The load will only take place if a disk
3246  file with the same name as the relation exists in the specified directory.  file with the same name as the relation exists in the specified directory.
# Line 2713  Line 3300 
3300      return $retVal;      return $retVal;
3301  }  }
3302    
3303  =head3 LoadMetaData  =head3 _LoadMetaData
3304    
3305  This method loads the data describing this database from an XML file into a metadata structure.  This method loads the data describing this database from an XML file into a metadata structure.
3306  The resulting structure is a set of nested hash tables containing all the information needed to  The resulting structure is a set of nested hash tables containing all the information needed to
# Line 3040  Line 3627 
3627      return $metadata;      return $metadata;
3628  }  }
3629    
3630  =head3 SortNeeded  =head3 _CreateRelationshipIndex
   
 C<< my $flag = $erdb->SortNeeded($relationName); >>  
   
 Return TRUE if the specified relation should be sorted during loading to remove duplicate keys,  
 else FALSE.  
   
 =over 4  
   
 =item relationName  
   
 Name of the relation to be examined.  
   
 =item RETURN  
   
 Returns TRUE if the relation needs a sort, else FALSE.  
   
 =back  
   
 =cut  
 #: Return Type $;  
 sub SortNeeded {  
     # Get the parameters.  
     my ($self, $relationName) = @_;  
     # Declare the return variable.  
     my $retVal = 0;  
     # Find out if the relation is a primary entity relation.  
     my $entityTable = $self->{_metaData}->{Entities};  
     if (exists $entityTable->{$relationName}) {  
         my $keyType = $entityTable->{$relationName}->{keyType};  
         Trace("Relation $relationName found in entity table with key type $keyType.") if T(3);  
         # If the key is not a hash string, we must do the sort.  
         if ($keyType ne 'hash-string') {  
             $retVal = 1;  
         }  
     }  
     # Return the result.  
     return $retVal;  
 }  
   
 =head3 CreateRelationshipIndex  
3631    
3632  Create an index for a relationship's relation.  Create an index for a relationship's relation.
3633    
# Line 3125  Line 3672 
3672      _AddIndex("idx$relationshipName$indexKey", $relationStructure, $newIndex);      _AddIndex("idx$relationshipName$indexKey", $relationStructure, $newIndex);
3673  }  }
3674    
3675  =head3 AddIndex  =head3 _AddIndex
3676    
3677  Add an index to a relation structure.  Add an index to a relation structure.
3678    
# Line 3171  Line 3718 
3718      $relationStructure->{Indexes}->{$indexName} = $newIndex;      $relationStructure->{Indexes}->{$indexName} = $newIndex;
3719  }  }
3720    
3721  =head3 FixupFields  =head3 _FixupFields
3722    
3723  This method fixes the field list for an entity or relationship. It will add the caller-specified  This method fixes the field list for an entity or relationship. It will add the caller-specified
3724  relation name to fields that do not have a name and set the C<PrettySort> value as specified.  relation name to fields that do not have a name and set the C<PrettySort> value as specified.
# Line 3209  Line 3756 
3756          # Here it doesn't, so we create a new one.          # Here it doesn't, so we create a new one.
3757          $structure->{Fields} = { };          $structure->{Fields} = { };
3758      } else {      } else {
3759          # Here we have a field list. Loop through its fields.          # Here we have a field list. We need to track the searchable fields, so we
3760            # create a list for stashing them.
3761            my @textFields = ();
3762            # Loop through the fields.
3763          my $fieldStructures = $structure->{Fields};          my $fieldStructures = $structure->{Fields};
3764          for my $fieldName (keys %{$fieldStructures}) {          for my $fieldName (keys %{$fieldStructures}) {
3765              Trace("Processing field $fieldName of $defaultRelationName.") if T(4);              Trace("Processing field $fieldName of $defaultRelationName.") if T(4);
# Line 3223  Line 3773 
3773                  # The data generator will use the default for the field's type.                  # The data generator will use the default for the field's type.
3774                  $fieldData->{DataGen} = { content => $TypeTable{$type}->{dataGen} };                  $fieldData->{DataGen} = { content => $TypeTable{$type}->{dataGen} };
3775              }              }
3776                # Check for searchability.
3777                if ($fieldData->{searchable}) {
3778                    # Only allow this for a primary relation.
3779                    if ($fieldData->{relation} ne $defaultRelationName) {
3780                        Confess("Field $fieldName of $defaultRelationName is in secondary relations and cannot be searchable.");
3781                    } else {
3782                        push @textFields, $fieldName;
3783                    }
3784                }
3785              # Plug in the defaults for the optional data generation parameters.              # Plug in the defaults for the optional data generation parameters.
3786              Tracer::MergeOptions($fieldData->{DataGen}, { testCount => 1, pass => 0 });              Tracer::MergeOptions($fieldData->{DataGen}, { testCount => 1, pass => 0 });
3787              # Add the PrettySortValue.              # Add the PrettySortValue.
3788              $fieldData->{PrettySort} = (($type eq "text") ? $textPrettySortValue : $prettySortValue);              $fieldData->{PrettySort} = (($type eq "text") ? $textPrettySortValue : $prettySortValue);
3789          }          }
3790            # If there are searchable fields, remember the fact.
3791            if (@textFields) {
3792                $structure->{searchFields} = \@textFields;
3793            }
3794      }      }
3795  }  }
3796    
3797  =head3 FixName  =head3 _FixName
3798    
3799  Fix the incoming field name so that it is a legal SQL column name.  Fix the incoming field name so that it is a legal SQL column name.
3800    
# Line 3260  Line 3823 
3823      return $fieldName;      return $fieldName;
3824  }  }
3825    
3826  =head3 FixNames  =head3 _FixNames
3827    
3828  Fix all the field names in a list.  Fix all the field names in a list.
3829    
# Line 3291  Line 3854 
3854      return @result;      return @result;
3855  }  }
3856    
3857  =head3 AddField  =head3 _AddField
3858    
3859  Add a field to a field list.  Add a field to a field list.
3860    
# Line 3326  Line 3889 
3889      $fieldList->{$fieldName} = $fieldStructure;      $fieldList->{$fieldName} = $fieldStructure;
3890  }  }
3891    
3892  =head3 ReOrderRelationTable  =head3 _ReOrderRelationTable
3893    
3894  This method will take a relation table and re-sort it according to the implicit ordering of the  This method will take a relation table and re-sort it according to the implicit ordering of the
3895  C<PrettySort> property. Instead of a hash based on field names, it will return a list of fields.  C<PrettySort> property. Instead of a hash based on field names, it will return a list of fields.
# Line 3387  Line 3950 
3950    
3951  }  }
3952    
3953  =head3 IsPrimary  =head3 _IsPrimary
3954    
3955  Return TRUE if a specified relation is a primary relation, else FALSE. A relation is primary  Return TRUE if a specified relation is a primary relation, else FALSE. A relation is primary
3956  if it has the same name as an entity or relationship.  if it has the same name as an entity or relationship.
# Line 3423  Line 3986 
3986      return $retVal;      return $retVal;
3987  }  }
3988    
3989  =head3 FindRelation  =head3 _FindRelation
3990    
3991  Return the descriptor for the specified relation.  Return the descriptor for the specified relation.
3992    
# Line 3454  Line 4017 
4017    
4018  =head2 HTML Documentation Utility Methods  =head2 HTML Documentation Utility Methods
4019    
4020  =head3 ComputeRelationshipSentence  =head3 _ComputeRelationshipSentence
4021    
4022  The relationship sentence consists of the relationship name between the names of the  The relationship sentence consists of the relationship name between the names of the
4023  two related entities and an arity indicator.  two related entities and an arity indicator.
# Line 3492  Line 4055 
4055      return $result;      return $result;
4056  }  }
4057    
4058  =head3 ComputeRelationshipHeading  =head3 _ComputeRelationshipHeading
4059    
4060  The relationship heading is the L<relationship sentence|/ComputeRelationshipSentence> with the entity  The relationship heading is the L<relationship sentence|/ComputeRelationshipSentence> with the entity
4061  names hyperlinked to the appropriate entity sections of the document.  names hyperlinked to the appropriate entity sections of the document.
# Line 3529  Line 4092 
4092      return $result;      return $result;
4093  }  }
4094    
4095  =head3 ShowRelationTable  =head3 _ShowRelationTable
4096    
4097  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
4098  table with three columns-- the field name, the field type, and the field description.  table with three columns-- the field name, the field type, and the field description.
# Line 3590  Line 4153 
4153      $htmlString .= "</ul>\n";      $htmlString .= "</ul>\n";
4154  }  }
4155    
4156  =head3 OpenFieldTable  =head3 _OpenFieldTable
4157    
4158  This method creates the header string for the field table generated by L</ShowMetaData>.  This method creates the header string for the field table generated by L</ShowMetaData>.
4159    
# Line 3615  Line 4178 
4178      return _OpenTable($tablename, 'Field', 'Type', 'Description');      return _OpenTable($tablename, 'Field', 'Type', 'Description');
4179  }  }
4180    
4181  =head3 OpenTable  =head3 _OpenTable
4182    
4183  This method creates the header string for an HTML table.  This method creates the header string for an HTML table.
4184    
# Line 3655  Line 4218 
4218      return $htmlString;      return $htmlString;
4219  }  }
4220    
4221  =head3 CloseTable  =head3 _CloseTable
4222    
4223  This method returns the HTML for closing a table.  This method returns the HTML for closing a table.
4224    
# Line 3667  Line 4230 
4230      return "</table></p>\n";      return "</table></p>\n";
4231  }  }
4232    
4233  =head3 ShowField  =head3 _ShowField
4234    
4235  This method returns the HTML for displaying a row of field information in a field table.  This method returns the HTML for displaying a row of field information in a field table.
4236    
# Line 3702  Line 4265 
4265      return $htmlString;      return $htmlString;
4266  }  }
4267    
4268  =head3 HTMLNote  =head3 _HTMLNote
4269    
4270  Convert a note or comment to HTML by replacing some bulletin-board codes with HTML. The codes  Convert a note or comment to HTML by replacing some bulletin-board codes with HTML. The codes
4271  supported are C<[b]> for B<bold>, C<[i]> for I<italics>, and C<[p]> for a new paragraph.  supported are C<[b]> for B<bold>, C<[i]> for I<italics>, and C<[p]> for a new paragraph.

Legend:
Removed from v.1.47  
changed lines
  Added in v.1.72

MCS Webmaster
ViewVC Help
Powered by ViewVC 1.0.3