There seems to be some interest on the use of SQL databases with Zope.<br><br>Lovelysystems is now using SQL databases as the primary storage for their applications. We use Zope and Postgres with Storm as ORM.<br>The main reason for switching to SQL database were speed issues with queries.<br>


<br>
Here is a short summary of my thougt&#39;s and experiences while using Storm and Zope for about 3 Month now.<br>
<br>
<br>
RelStorage:<br>
Relstorage doesn&#39;t solve the speed problems. Doing queries with SQL is much faster than doing it with ZODB. If you work with a lot and
with large BTrees you need to load them all into the memory of each Zope client. This has to be done with Relstorage too.<br>
<br>
<br>
Indexes:<br>
You don&#39;t need to implement catalog indexes, this is all
done on the database side. When implementing and using your content types, at first you don&#39;t need to think about indexes,
later you optimize the database without touching your python code.<br>

<br>
A speed example :<br>

We had to find similar users based on items a user has collected. Doing
this with ZODB took minutes to calculate for users with a lot of items.
We had to implement a lot of code to do the calculation asynchronously
to not block the users request.<br>

Doing the same with SQL was possible with a single (of course complex)
query within 300ms, no async things needed, just implement the query
and optimize the indexes on the server, finished ! Relstorage will not
help you here.<br>
<br>
<br>
Content implementation:<br>
While
we are porting our existing ZODB based packages to SQL, we found
that implementing them with Storm is as easy as using ZODB. We still can use
the full power of Zope&#39;s component architecture. This is because Storm objects are extremely easy to implement. You can implement a storm object like a Persistent object, just derive from Storm instead of Persistent, add __storm_table__ and define the properties as Storm properties.<br>

<br>For me a big mistake when switching from ZODB to SQL is trying to use
the container pattern at any cost.<br>A container is nothing but a&nbsp; 1:N relation and this is exactly what an SQL database provides : Relations<br>
<br>
class Content(Storm):<br>
&nbsp;&nbsp;&nbsp; id = Int(primary=True)<br>
&nbsp;&nbsp;&nbsp; content = ReferenceSet(id, &#39;Contained.somethingId&#39;)<br>
c = Content()<br><br>
Now you can<br>&nbsp;- add data : c.content.add(content)<br>&nbsp;- iterate : for a in c.content:<br>
&nbsp;- search : c.content.find(...)<br>
&nbsp;- sort : c.content.find().sort_by(...)<br>
&nbsp;- do anything a Storm ResultSet is providing<br>
<br>But of course it is possible to put an adapter around the Content class which will provide IContainer.<br>
<br><br>
Annotation:<br>
Annotations are 1:1 relations, so it&#39;s as easy as the above.<br>
We use annotations like simple adapters to other tables.<br>
<br>
class ToBeAnnotated(Storm):<br>
&nbsp;&nbsp;&nbsp; interface.implements(ICanHaveData)<br>
&nbsp;&nbsp;&nbsp; id = Int(primary=True)<br>
<br>
 Note that the &quot;annotated&quot; storm table is implemented as an adapter :<br>
<br>
class Data(Storm):<br>
&nbsp;&nbsp;&nbsp; interface.implements(IData)<br>

&nbsp;&nbsp;&nbsp; interface.adapts(ICanHaveData)<br>

&nbsp;&nbsp;&nbsp; id = Int(primary=True)<br>
&nbsp;&nbsp;&nbsp; __parent__ = Reference(id, ToBeAnnotated.id)<br>
&nbsp;&nbsp;&nbsp; def _init__(self, context):<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # a dummy to make the adapter happy<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pass<br>
<br>
We can now register &quot;Data&quot; as an adapter.<br>
We use a special adapter factory like zope.annotation.factory to autocreate adapted content.<br>
<br>def contentAdapter(table, autocreate=True):<br>&nbsp;&nbsp;&nbsp; # an adapter on content for content contained in other tables. Just like<br>&nbsp;&nbsp;&nbsp; # the annotation adapter, an instance is created if autocreate is True.<br>&nbsp;&nbsp;&nbsp; adapts = component.adaptedBy(table)<br>

&nbsp;&nbsp;&nbsp; if adapts is None:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; raise TypeError(&quot;Missing &#39;zope.component.adapts&#39; on table&quot;)<br>&nbsp;&nbsp;&nbsp; @component.adapter(list(adapts)[0])<br>&nbsp;&nbsp;&nbsp; @interface.implementer(list(component.implementedBy(table))[0])<br>

&nbsp;&nbsp;&nbsp; def getAdapter(context):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; unsafeContext = removeSecurityProxy(context)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; obj = getStore().find(table, table.__parent__ == unsafeContext).one()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if obj is None and autocreate:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; obj = table(context)<br>

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; obj.__parent__ = context<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return obj<br>&nbsp;&nbsp;&nbsp; return getAdapter<br><br>Now you can define a factory for the adapter:<br><br>dataFactory = contentAdapter(Data)<br><br>And register &quot;dataFactory&quot; as an adapter.<br>

<br><br>DublinCore:<br>If you want to use the full DublinCore implementation from Zope you need to do a generic implementation.<br>Usually only parts of the DublinCore interface is used.<br>We usually implement IDCTimes and IDCDescriptiveProperties. All you need to do for this is :<br>

<br>class DCStormContent(Storm):<br>&nbsp;&nbsp;&nbsp; interface.implements(IDCTimes, IDCDescriptiveProperties)<br>&nbsp;&nbsp;&nbsp; created = DateTime()<br>
&nbsp;&nbsp;&nbsp; modified = DateTime()<br>&nbsp;&nbsp;&nbsp;
title = Unicode()<br>&nbsp;&nbsp;&nbsp; description = Unicode()<br><br>That&#39;s it!<br>You can now use IDCTimes and IDCDescriptiveProperties for your formlib form_fields.<br><br>There are two way&#39;s to update &quot;modified&quot; :<br>

&nbsp;- write an event handler for ObjectModifiedEvent<br>&nbsp;- do it on the database side with triggers<br>I prefer using the event handler because the database trigger is doing the update only when writing to the database which can be to late.<br>

<br><br>
Schema&#39;s and Storm objects:<br>
We don&#39;t use schema&#39;s to create our Storm objects or the database table
from it. Right now for us it is not worth the time to implement such a
feature.<br>
<br>
<br>
Traversing and URL&#39;s:<br>
Usually our customers whant to have special URL&#39;s for their pages. In
any way (ZODB or SQL) we need to implement special traverser&#39;s to
provide the URL&#39;s. Usually we use z3c.traverser to do this.<br>
Because of the special URL&#39;s we also need to implement absolute URL adapters.<br>
<br>
<br>
Transaction handling:<br>
Storm already has a DataManager for zope&#39;s transaction package.<br>All you need to do is to register a utility for each of the database you want to use.<br>
<br>
<br>
ZMI:<br>
Hmm, don&#39;t work out of the box. If really needed we build traversers for the ZMI.<br><br>
<br>
Data transparency:<br>
At any time you can use any database administration tool you like to directly view and/or manipulate you data in the database.<br>
<br><br>
Jürgen<br>