Q: How do I use ToRootTable, PseudoRoot, IgnoreRoot, etc.?
Applies to: 1.x
When to use ToRootTable, ToClassTable, PseudoRoot, IgnoreRoot, etc. is is one of the most confusing parts of the version 1.x mapping language. Fortunately, it is gone in version 2.0. I'll try to explain.
XML-DBMS needs to know the columns in the primary (candidate) key in the root table. It needs this information so it can build SELECT statements over the root table when retrieving data. XML-DBMS can get this information from two places: the PseudoRoot element and the ToRootTable element. Which one you use depends on whether you use an ignored root element or not.
Root element is significant
For example, suppose you want to send a single sales order in an XML document:
<SalesOrder Number="123"> <OrderDate>1/1/01</OrderDate> <Customer> ... </Customer> <LineItem Number = "1"> <Quantity>2</Quantity> <Part Number = "abc"> ... </Part> </LineItem> ... </SalesOrder>
In this case, the root element (<SalesOrder>) is significant and you don't want to ignore it. Map the SalesOrder element type using a <ToRootTable> element in the <ClassMap> element. Notice that the candidate key information is inside the <ToRootTable> element.
<ClassMap> <ElementType Name="SalesOrder" /> <ToRootTable> <Table Name="Orders" /> <CandidateKey Generate="No" > <Column Name="OrderNum" /> </CandidateKey> </ToRootTable> ... </ClassMap>
Root element is not significant
Now suppose you want to send multiple sales orders in a single XML document:
<Orders> <SalesOrder> ... </SalesOrder> <SalesOrder> ... </SalesOrder> <SalesOrder> ... </SalesOrder> </Orders>
In this case, you don't care about the <Orders> element -- it's not mapped to anything in the database -- so you can map it with an <IgnoreRoot> element:
<IgnoreRoot> <ElementType Name="Orders" /> ... </IgnoreRoot>
However, you do care about the <SalesOrder> element, so you need to declare this as a "pseudo-root" element. It is called a pseudo-root element because it would be the root element if XML allowed me to have multiple root elements in the same document. Here's the declaration. Notice that the candidate key information is in the <PseudoRoot> element:
<IgnoreRoot> <ElementType Name="Orders" /> <PseudoRoot> <ElementType Name="SalesOrder"> <CandidateKey Generate="No" > <Column Name="OrderNum" /> </CandidateKey> </PseudoRoot> </IgnoreRoot>
In the <ClassMap> element for the <SalesOrder> element, use the <ToClassTable> element, which does not contain candidate key information:
<ClassMap> <ElementType Name="SalesOrder" /> <ToClassTable> <Table Name="Orders" /> </ToClassTable> ... </ClassMap>
Retrieving documents and root table declarations
This leads us to one more problem that is commonly encountered -- a NullPointerException in DBMSToDOM:
java.lang.NullPointerException at de.tudarmstadt.ito.xmldbms.Map.getRootTableMap(Map.java:428) at de.tudarmstadt.ito.xmldbms.DBMSToDOM.retrieveTableData (DBMSToDOM.java:360) at de.tudarmstadt.ito.xmldbms.DBMSToDOM.retrieveDocument (DBMSToDOM.java:271)
Although the NullPointerException is due to a bug, the fix isn't much more helpful -- it throws an InvalidMapException telling you that your map is incorrect.
The problem is that if you want to use a given table as a root table, that table/element type must be mapped with a ToRootTable element (if there is no ignored root) or a PseudoRoot element (if there is an ignored root).
For example, suppose you map the <Part> element in the sales order example using <ToClassTable>. This is OK as long as <Part> is nested inside <LineItem>. However, if you just want to retrieve information from the Parts table -- so <Part> is the root element -- you will get the above NullPointerException.
How to fix this depends on whether the XML document you want to create has an ignored root element or not. If it does not have an ignored root, then map <Part> with <ToRootTable>.
<ClassMap> <ElementType Name="Part" /> <ToRootTable> <Table Name="Parts" /> <CandidateKey Generate="No" > <Column Name="PartNum" /> </CandidateKey> </ToRootTable> ... </ClassMap>
If it does have an ignored root, add a <PseudoRoot> element for <Part>:
<IgnoreRoot> <ElementType Name="Orders" /> <PseudoRoot> <ElementType Name="SalesOrder"> <CandidateKey Generate="No" > <Column Name="OrderNum" /> </CandidateKey> </PseudoRoot> <PseudoRoot> <ElementType Name="Part"> <CandidateKey Generate="No" > <Column Name="PartNum" /> </CandidateKey> </PseudoRoot> </IgnoreRoot>
and map <Part> using <ToClassTable>:
<ClassMap> <ElementType Name="Part" /> <ToClassTable> <Table Name="Parts" /> </ToClassTable> ... </ClassMap>
Notice two things. First, an <IgnoreRoot> element can have multiple <PseudoRoot> children. Second, the generated file will use <Orders> as its root, which is probably not what you want. The solution is to use a neutrally-named root element, such as <JustARootElement> and map this as the ignored root element, or create different map files for each case (yech).
What should I do?
For maximum flexibility, the best thing to do is:
If you are not using an ignored root element, use <ToRootTable> in the <ClassMap> elements for all class elements.
If you are using an ignored root element, declare all class elements as pseudo-root elements. That is, include a <PseudoRoot> element for each class element and use <ToClassTable> in the <ClassMap> for each class element.
Version 2.0
Fortunately, all this is fixed in version 2.0, so if you can use version 2.0, you should. In particular, version 2.0 has the following fixes:
Any element can be a root element without a special declaration.
Ignored root elements don't need to be declared. When transferring data from an XML document to the database, XML-DBMS simply ignores top-level elements (they may be nested more than one level deep) until it finds an element type that has been mapped.
When transferring data from the database to an XML document, you can declare the chain (one or more levels deep) of ignored root elements (called "wrapper elements") that you want to use to wrap the results.