<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Pervasive Code &#187; sql</title>
	<atom:link href="http://www.pervasivecode.com/blog/category/sql/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.pervasivecode.com/blog</link>
	<description>Jamie Flournoy's Software Development Blog</description>
	<lastBuildDate>Wed, 01 Feb 2012 06:11:00 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Sphinx Search init script for Centos 5.1</title>
		<link>http://www.pervasivecode.com/blog/2008/04/14/sphinx-search-init-script-for-centos-51/</link>
		<comments>http://www.pervasivecode.com/blog/2008/04/14/sphinx-search-init-script-for-centos-51/#comments</comments>
		<pubDate>Mon, 14 Apr 2008 06:18:11 +0000</pubDate>
		<dc:creator>Jamie Flournoy</dc:creator>
				<category><![CDATA[CentOS]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[databases]]></category>
		<category><![CDATA[postgresql]]></category>
		<category><![CDATA[servers]]></category>
		<category><![CDATA[sql]]></category>

		<guid isPermaLink="false">http://www.pervasivecode.com/blog/2008/04/14/sphinx-search-init-script-for-centos-51/</guid>
		<description><![CDATA[Sphinx search is pretty new, and as a result I was unable to find a nice convenient package for it for CentOS 5.1. This is problematic since there is no init script included with the source tarball, and the issue of updating the index is the sysadmin and developer&#8217;s problem, and cannot be configured to [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.sphinxsearch.com/">Sphinx search</a> is pretty new, and as a result I was unable to find a nice convenient package for it for CentOS 5.1. This is problematic since there is no init script included with the source tarball, and the issue of updating the index is the sysadmin and developer&#8217;s problem, and cannot be configured to simply update the index when the data changes.<br />
<span id="more-65"></span><br />
The second problem (updates) is one I punted on; for now I have a cron job rebuilding the entire index every 5 minutes, which will probably be replaced with something smarter and lower-latency at a later time.</p>
<p>The first problem (no init script) is easy to solve, but apparently nobody has done so for CentOS 5.1 and published it. So, here is <a href="/code/centos_sphinx_init_script.txt">my CentOS 5.1 init script for the Sphinx Search server</a>. It is known to work with version 0.9.8-rc2.</p>
<p>BTW, the alternative solution to the problem of a daemon not having a System V init script is to just put some extra junk in <code>/etc/rc.local</code>. That is the quick and dirty solution, and is undesirable for several reasons:</p>
<ol>
<li>You can&#8217;t easily stop or restart the service, because it&#8217;s not a service as far as the OS knows; it&#8217;s just some junk in a script that got run a while ago.</li>
<li>You can&#8217;t use <a href="http://www.centos.org/docs/5/html/5.1/Deployment_Guide/s1-services-chkconfig.html">chkconfig</a> or its GUI cousin with the creative name, <a href="http://www.centos.org/docs/5/html/5.1/Deployment_Guide/s1-services-serviceconf.html">The Services Configuration Tool</a>, to control it and tie it to specific runlevels.</li>
</ol>
<p>(System V runlevels and init scripts are useful, even if you don&#8217;t need all of the runlevel functionality. The stop/start/restart PID stuff is useful by itself.)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.pervasivecode.com/blog/2008/04/14/sphinx-search-init-script-for-centos-51/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Document Databases &#8211; New Kids on an Old Block</title>
		<link>http://www.pervasivecode.com/blog/2008/02/16/document-databases-new-kids-on-an-old-block/</link>
		<comments>http://www.pervasivecode.com/blog/2008/02/16/document-databases-new-kids-on-an-old-block/#comments</comments>
		<pubDate>Sat, 16 Feb 2008 06:16:16 +0000</pubDate>
		<dc:creator>Jamie Flournoy</dc:creator>
				<category><![CDATA[architecture]]></category>
		<category><![CDATA[databases]]></category>
		<category><![CDATA[gfs]]></category>
		<category><![CDATA[nfs]]></category>
		<category><![CDATA[oracle]]></category>
		<category><![CDATA[postgresql]]></category>
		<category><![CDATA[servers]]></category>
		<category><![CDATA[sql]]></category>

		<guid isPermaLink="false">http://www.pervasivecode.com/blog/2008/02/16/document-databases-new-kids-on-an-old-block/</guid>
		<description><![CDATA[There&#8217;s a new crop of databases that has appeared lately, under the rubric of &#8220;document databases&#8221;, and there&#8217;s quite a lot of enthusiasm for them given that they tend to be slow and very feature-poor compared to the SQL RDBMSs that are the typical persistence mechanism for web applications. What&#8217;s mainly appealing about them is [...]]]></description>
			<content:encoded><![CDATA[<p>There&#8217;s a new crop of databases that has appeared lately, under the rubric of &#8220;document databases&#8221;, and there&#8217;s quite a lot of enthusiasm for them given that they tend to be slow and very feature-poor compared to the SQL RDBMSs that are the typical persistence mechanism for web applications. What&#8217;s mainly appealing about them is that they are easy to use, and theoretically quite scalable, compared to the traditional &#8220;one big SQL database server&#8221; approach.<br />
<span id="more-59"></span></p>
<p>But the simplicity of these new document databases is tied to some significant trade-offs in the current implementations. And so I&#8217;m going to try and put them into context with some of the other data persistence options that have been around for a while, but which aren&#8217;t currently getting as much hype as document databases. Hopefully that will help all of us to understand how these new and evolving document databases can be useful to us, and what the alternatives are in areas where they may not fit well.</p>
<p>I&#8217;d first like to try and deconstruct a false dichotomy that I&#8217;ve noticed being used in arguments in favor of some of these new databases. That dichotomy casts SQL RDBMSs (such as MySQL, Oracle, PostgreSQL, MS SQL Server, etc.) as big, complicated, and hard to scale, compared to document databases which are small, simple, and easy to scale. The main problem with this dichotomy is that there are far more choices than just two. Each database product embodies a set of design choices, and although there is some clustering of decisions into general types of product, the boundaries are a lot fuzzier than a product evangelist might have you believe.</p>
<p>Furthermore, trade-offs made early in a product&#8217;s lifetime may have been altered over time. A good example is the no-longer-true characterization of MySQL being fast but not reliable, vs. PostgreSQL being reliable but not fast. In reality, recent releases of both products are moving toward being very fast and very reliable.</p>
<p>Because there are so many database products out there, I&#8217;m going to have to fall back on a small subset of example products, as illustrations of issues that may or may not exist in a particular product you&#8217;re looking at. The key for an application architect evaluating a persistence mechanism is to understand the abstract concepts, and to figure out which ones matter to your current application. That will let you select a product (or a combination of products, including some custom code perhaps) that suits you. As with all aspects of architecture, there is no cookbook you can use, and in six months all the options will change. You have to analyze your needs first, and then get your hands dirty with evaluation second.</p>
<p>So, let&#8217;s start deconstructing some of the examples into design decisions.</p>
<p><b>SQL RDMBS</b></p>
<p>This is the traditional choice for web applications. You get a remote query language, so you can specify in great detail what you want to retrieve. You get very precise control over data representation, including some that may be burdensome if you&#8217;re not concerned with internationalization, multiple currencies, and time zones. You get a lot of control over performance and a lot of information about how things work at a low level inside the database, from indexes to data page size to transaction logs and checkpoint frequency. Most of them include ACID transaction support, which is nice for reliability, but which usually obligates you to implement a backup scheme or else they will eventually stop accepting new transactions and/or run out of disk space.</p>
<p>Some of the design drawbacks include:<br />
- the use of local storage, for performance and for a guarantee that data has been committed to disk<br />
- the use of a high level query language (SQL) and a query optimizer, so the specific process the database uses to satisfy your query is not in your face (and thus may be surprisingly inefficient, if you aren&#8217;t familiar with how it works)<br />
- the use of a proprietary network protocol, which means that you need a special client library for just that one product, which may or may not implement all of the features that the server offers (such as encryption)</p>
<p>However, there are some variations that make the edges of this category fuzzier. There are ACID-compliant SQL RDBMSs that have no network layer, and are very lightweight; in some cases they may not even support concurrent access. Examples include <a href="http://hsqldb.org/">HSQLDB</a> and <a href="http://www.sqlite.org/">SQLite</a>. </p>
<p><b>Networked Filesystem</b></p>
<p>This is typically used for accessing shared file servers, or allowing &#8220;thin client&#8221; behavior so that users can get to their own environment and data from any given endpoint. Examples include NFS, SMB, AFP, <a href="http://www.redhat.com/gfs/">GFS</a>, and quite a few others. The main advantage of these systems is that the remote filesystem is represented as being directly connected to the local system, while also being available to other users or other client systems who are connected to the same remote system.</p>
<p>Trade-offs of this design include:<br />
- performance on a LAN may be good, but over a slow, high latency link may be very poor<br />
- there is usually no ACID transaction support, just file locking<br />
- file ownership and permissions can be very hard to manage<br />
- if the file server goes offline, the entire local system may hang or crash</p>
<p>In particular, content indexing, complex querying, and data integrity features are generally not offered. You can layer that on top, though, but that layer will not necessarily work if it was originally designed to work on local filesystems. In particular I&#8217;m thinking about DBM files; they&#8217;re fast and easy to use but not all of them will work properly with files located on a network filesystem.</p>
<p>Also, directory scanning performance can be very poor if thousands of files are located in a single directory; listing all files starting with the letter T may actually require the entire directory to be retrieved and filtered on the client side.</p>
<p>Variations include FTP and WebDAV, which are not intended to simulate a local filesystem, but instead have filesystem-like semantics. Some operating systems will mount them as remote filesystems anyway, for ease of use for viewing and copying files, but it&#8217;s not possible to lock a remote file, so safe multiuser access is not possible.</p>
<p><b>Object Database</b></p>
<p>Object databases offer a direct representation of an application&#8217;s data in almost exactly the same form that exists in memory. Whereas a relational database stores data in tabular form regardless of the particulars of a client application, an object database stores data in the same form that the application uses. The exception to this is in the representation of references to other objects; at some level these encapsulate pointers to the memory address of the data in the application&#8217;s address space, and this must be substituted with a pointer to the location in the database&#8217;s storage system before storing it.</p>
<p>An object database will not handle time zones or internationalization, but nor will it complicate those matters if the application handles those already. The data is simply stored as-is. Also, object databases typically do offer ACID transaction support.</p>
<p>Aside from the conceptual simplicity of the similar data model, one major bonus of an object database is that the use of pointers makes data retrieval extremely fast; rather than parsing a query and searching indexes for the on-disk location of a desired object, the application can simply ask the database for it by its reference.</p>
<p>The big trade-offs here are twofold:</p>
<p>First, the lack of indirection through a query language and an indexing system mean that the application developer must anticipate all of the queries that will be needed, and incorporate collections into the the object graph that will be used to get to the stored objects. Otherwise the application&#8217;s object model will need to be updated frequently to include these later.</p>
<p>Second, altering the application&#8217;s object model and then retrieving data stored using older code can be very complex. Because the stored objects and application&#8217;s code are out of sync in this situation, additional application code must be written to convert existing stored objects into the new representation and persist them back to the database.</p>
<p>Combine those two trade-offs, and it&#8217;s clear that the performance benefit comes with the price of considerable additional application development effort.</p>
<p>Also, an object database is by nature bound to a single application, rather than being a point of integration between multiple applications. Any attempt to create a shared code library that manages access to the object database introduces potential &#8220;impedance mismatches&#8221; between each application and the shared object model, which reduces the simplicity that an object database offers in comparison to a relational database.</p>
<p><b>Document Database</b></p>
<p>Arguably a rejection of relational technology, document databases offer several advantages compared to the three classes of database previously mentioned. Documents need not be internally represented as a flat set of key-value pairs as seen in a SQL RDBMS; for example, the document may be an XML document. Queries are possible and may even use a standardized query language to express the conditions for matching desirable documents. Documents may have internal structure that is understood by the database server (so that it can query against the document&#8217;s contents), as in the case of an XML database, or they may have an external metadata structure consisting of key-value pairs, or both.</p>
<p>The drawbacks of this type of system derive from the fact that it is similar in many ways to each of the other database types.</p>
<p>Querying ability means that the server must incorporate some kind of indexing system for performance reasons, which means that the document must either internally or externally conform to some sort of standard data model. Some document database systems simply omit querying except by the document&#8217;s main ID (similar to a SQL primary key, a network filesystem&#8217;s filename, or an object database&#8217;s storage reference). This has the same drawback as with an object database: the application must take on the responsibility of managing querying, searching, and sorting itself, across the network.</p>
<p>The similarity to a networked filesystem may also have scalability benefits; if referential data integrity is not provided, then documents can be located on any remote system, and partitioning is simple. However, distributed queries will still need to be managed at the application level, and potentially any transaction becomes a distributed transaction, since a changed document on one server may be referenced from any number of documents in any number of other servers. (This is also true of the other systems, though.)</p>
<p>Still, because a document database is closest to a networked filesystem, it may be suitable for simple requirements where a relational database or object database seems to complex and slow, but where the bare-bones functionality of a networked filesystem is too simple. The compromise of a binary file with simple additional metadata or properties attached &#8220;out of band&#8221; with the data itself, or of a structured document format that is flexible but not ideal, may be acceptable if sophisticated querying is possible as a result.</p>
<p>It&#8217;s hard to provide good examples of document databases, because the category is very broad, and includes a lot of simple projects that provide just a little functionality above and beyond what WebDAV already provides. But a few that I&#8217;ve heard of recently include CouchDB, SimpleDB, and RDDB.</p>
<p>CouchDB imposes a simple key-value data structure on document content, but no internal document schema or grouping of documents by type. It does offer indexing and querying. Notably, it also offers transparent replication, at a field level (changes to two different fields in two copies of the same document are synchronized to both copies).</p>
<p>Amazon SimpleDB similarly imposes a key-value structure, though one key can have multiple values. It too offers a query language, and indexing. Because it&#8217;s built on Amazon&#8217;s S3 service, transparent replication is also included.</p>
<p><b>Future Prospects</b></p>
<p>The main complaints that I&#8217;ve seen directed at relational databases involve two things: one, the difficulty of scaling them up, and two, the restrictive data model. Sharding (a.k.a. data partitioning) is the usual remedy for scaling problems, but that requires the elimination of referential integrity in the SQL RDBMSs I&#8217;m aware of, and requires distributed transactions in order to preserve ACID transaction properties across denormalized copies of the modified data.</p>
<p>Interestingly, these issues are the same across the board, regardless of database type. Either you abandon transactions, or you move them up to a level that&#8217;s aware of the data partitioning. I see no reason why these and other high-end RDBMS features couldn&#8217;t be offered in a proxy layer that possibly even contains the query processing as well.</p>
<p>One way to approach this is to build a closed system with a given set of features and a limited API that permits a single query language. This seems to be the way that CouchDB and SimpleDB are approaching the problem.</p>
<p>Another way to approach this problem is to simply say that the storage back-ends of relational databases could be enhanced to incorporate built-in transparent partitioning. I don&#8217;t think that SQL RDBMSs will abandon the concept of a table schema any time soon, but there&#8217;s no reason why products that already include XML query and indexing capabilities and free-form natural language indexing (a.k.a. Full Text Search) couldn&#8217;t also include indexing capabilities for simple key-value structured data inside a single column of semi-structured data, giving most of the same functionality as a document database.</p>
<p>Given that, the remaining limitation of a SQL RDBMSs is the requirement that the back-end storage system be located on a disk drive physically connected to the same server, and that the storage be touched only by processes running on the same server together so that they can coordinate access to the data.</p>
<p>For now, though, document databases look like they can be very useful for certain types of persistence requirements; I don&#8217;t see them as a viable substitute for everything that a SQL RDBMS does, but that perception is limited mainly by the choices at hand. CouchDB looks like the most generally useful option so far, though I&#8217;d like to see the addition of optional schemas (opt-in on a per-object level, as seen in LDAP), and/or a pluggable language option. (It seems that everyone using a document database is also enamored of their application language and dislikes the idea of putting logic in the data tier unless it&#8217;s written in the same language.)</p>
<p>I welcome your comments &#8211; this is mostly a brain dump of things I&#8217;ve seen before to help myself and others contextualize the new document databases, and document databases are evolving too rapidly for me to keep up with all of them on my own.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.pervasivecode.com/blog/2008/02/16/document-databases-new-kids-on-an-old-block/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Acts_as_tsearch adjustments needed for PostgreSQL 8.3rc2</title>
		<link>http://www.pervasivecode.com/blog/2008/01/24/acts_as_tsearch-adjustments-needed-for-postgresql-83rc2/</link>
		<comments>http://www.pervasivecode.com/blog/2008/01/24/acts_as_tsearch-adjustments-needed-for-postgresql-83rc2/#comments</comments>
		<pubDate>Thu, 24 Jan 2008 20:00:23 +0000</pubDate>
		<dc:creator>Jamie Flournoy</dc:creator>
				<category><![CDATA[databases]]></category>
		<category><![CDATA[postgresql]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[ruby on rails]]></category>
		<category><![CDATA[sql]]></category>

		<guid isPermaLink="false">http://www.pervasivecode.com/blog/2008/01/24/acts_as_tsearch-adjustments-needed-for-postgresql-83rc2/</guid>
		<description><![CDATA[Just a quick note: acts_as_tsearch needs some guidance to work with PostgreSQL 8.3 due to changes in tsearch2 integration.

I&#8217;m pretty close to tossing out acts_as_tsearch and rolling my own (trigger-based) tsearch2 plugin, but for now I&#8217;m just sticking with it and checking out the PostgreSQL 8.3 release candidate.
I was able to build 8.3rc2 on Mac [...]]]></description>
			<content:encoded><![CDATA[<p>Just a quick note: acts_as_tsearch needs some guidance to work with PostgreSQL 8.3 due to changes in tsearch2 integration.<br />
<span id="more-56"></span><br />
I&#8217;m pretty close to tossing out acts_as_tsearch and rolling my own (trigger-based) tsearch2 plugin, but for now I&#8217;m just sticking with it and checking out the PostgreSQL 8.3 release candidate.</p>
<p>I was able to build 8.3rc2 on Mac OS X 10.5.1 from the tarball sources with the instructions in the INSTALL document, no hitches whatsoever. Because I have 8.2 installed via MacPorts, there were no file conflicts (different install directories, data directories, etc.), so all I had to due was shut down the 8.2 server and start the 8.3rc2 server and it was ready to go.</p>
<p>Unfortunately, acts_as_tsearch didn&#8217;t work properly the way I had used with with 8.2. The issue appears to be that the tsearch2 locale called &#8216;default&#8217; is gone, which is what acts_as_tsearch uses if you don&#8217;t specify something else. The default locale value is now located in postgresql.conf. Using that value as an explicit locale in the acts_as_tsearch declaration in my model class solved the problem. The code change looks like this:</p>
<p><b>OLD:</b><br />
<code>acts_as_tsearch :fields => ["subject","body"]</code></p>
<p><b>NEW:</b><br />
<code>acts_as_tsearch :vector => {:fields => ["subject","body"], :locale => 'pg_catalog.english'}</code></p>
<p>Like I said, due to the fact that acts_as_tsearch is designed to hide the complexity of tsearch2, it is not well suited to my somewhat complex requirements. So, I&#8217;m ditching it in favor of custom code, which I hope to plugin-ize and release some time later. So, this change is necessary but might not be sufficient for your own project. But I hope it helps you get started on upgrading successfully.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.pervasivecode.com/blog/2008/01/24/acts_as_tsearch-adjustments-needed-for-postgresql-83rc2/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>ActiveRecord: the Visual Basic of Object Relational Mappers</title>
		<link>http://www.pervasivecode.com/blog/2007/10/04/activerecord-the-visual-basic-of-object-relational-mappers/</link>
		<comments>http://www.pervasivecode.com/blog/2007/10/04/activerecord-the-visual-basic-of-object-relational-mappers/#comments</comments>
		<pubDate>Fri, 05 Oct 2007 02:07:53 +0000</pubDate>
		<dc:creator>Jamie Flournoy</dc:creator>
				<category><![CDATA[architecture]]></category>
		<category><![CDATA[articles]]></category>
		<category><![CDATA[databases]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[perl]]></category>
		<category><![CDATA[postgresql]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[ruby on rails]]></category>
		<category><![CDATA[sql]]></category>

		<guid isPermaLink="false">http://www.pervasivecode.com/blog/2007/10/04/activerecord-the-visual-basic-of-object-relational-mappers/</guid>
		<description><![CDATA[I&#8217;ve been working with Ruby on Rails intensively for several months, and I&#8217;ve finally found a place where Rails can&#8217;t readily be extended to do what I want. It&#8217;s ActiveRecord, which is probably the most controversial part of Rails.
I&#8217;m reminded of a James Gosling quote disparaging Microsoft tools, particularly Visual Basic: &#8220;The easy stuff is [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve been working with Ruby on Rails intensively for several months, and I&#8217;ve finally found a place where Rails can&#8217;t readily be extended to do what I want. It&#8217;s ActiveRecord, which is probably the most controversial part of Rails.</p>
<p>I&#8217;m reminded of a <a href="http://en.wikipedia.org/wiki/James_Gosling">James Gosling</a> quote disparaging Microsoft tools, particularly Visual Basic: &#8220;The easy stuff is easy, but the hard stuff is impossible.&#8221; There&#8217;s a parallel between VB and Rails in this instance, in that <em>if you only let yourself use the high level tools</em>, the hard stuff is impossible, but the designers specifically tell you to do the hard stuff using a lower level toolset. The controversy that surrounds &#8220;X can&#8217;t do everything, therefore it sucks&#8221; should really be focusing on the feasibility of going through that trapdoor to do things &#8220;the hard way&#8221;. This is what <a href="http://en.wikipedia.org/wiki/Borland_Delphi">Delphi</a> did, which is why so many folks chose it over VB; it made the hard stuff easier.</p>
<p><span id="more-46"></span></p>
<p>Here&#8217;s the task I need to accomplish, for which ActiveRecord is not well suited: complex queries involving SQL functions and multiple-table joins. I want to join a few tables together, order by a SQL function, include with each result row the result of a SQL function that operates on each row, and have all that come back as a graph of high-level objects.</p>
<p>Despite my attempts to use plugins, extend and/or fix bugs in those plugins, and to dig through the ActiveRecord source to figure out what the documentation won&#8217;t tell me, I was unable to get it to work. Most of the parts of what I wanted was possible: acts_as_tsearch cleverly weaves SQL functions into a high-level ActiveRecord::Base.find calls; paginating_find provides a very convenient pagination API on top of ActiveRecord::Base.find, and ActiveRecord includes some clever association tricks such as automatic many-to-many relationships (has_and_belongs_to_many), eager loading of associated records using a join (via the :include option to ActiveRecord::Base.find), and a fairly low-level :joins option that lets you add tables to a &#8216;find&#8217; query which can be used in your :conditions. Problem is, they don&#8217;t all work together in a fancy way.</p>
<p>Really, the issue in this case is related to the design choices that went into ActiveRecord.</p>
<p>Some ORMs (object-relational mappers) are designed in a modular fashion: there is a part that helps you describe the relationships between your model objects, a part that helps you construct queries, and a part that does the storage and retrieval. Sometimes there&#8217;s another part that uses your description of object relationships to create an empty database with the appropriate data model, or that looks at an existing database and creates an object model that matches it. Sometimes there&#8217;s an import/export tool for bulk data loading or dumping as well.</p>
<p>ActiveRecord has the first three functions integrated (which has benefits and drawbacks compared to a more modular approach), has a very isolated schema manipulation module, and has a somewhat isolated data loader tool.</p>
<p>The relationships are explicitly declared in source code using associations: has_one, has_many, belongs_to, and has_and_belongs_to_many. These are pretty fancy and provide some convenience features that make the associations appear as object collections, such that changing the collection and saving it turns into insert/delete/update activity in the database.</p>
<p>Query construction is basically tied to the objects themselves, in a way that greatly simplifies star-join queries, but which handles only the simplest joins across multiple tables, and is barely able to handle self-referential joins at all. So, you can easily load an object (or group of similar objects) and associated objects, but OLAP-style queries (&#8220;what are the top 5 states where customers are located who have bought classical CDs within 2 weeks of their release using American Express and had them shipped as gifts via UPS 3-day Select?&#8221;) are impossible. Oddly, views, functions, and stored procedures could bridge the gap between real-world data models and ActiveRecord&#8217;s limited set of association types, but they are not supported either.</p>
<p>The storage and retrieval code is inseparable from the query code, and so it is not possible to examine and modify the final SQL before it is executed, nor is it possible to provide an arbitrary query and have the results be parsed into an object graph based on the associations you have defined. The code that would allow these features appears to exist and be sufficiently well designed to allow this with a fairly small amount of changes to ActiveRecord. However, it is currently (as of Rails 1.2.3, which is the current release) not part of the documented API and is declared private.</p>
<p>There is a limited facility for constructing simple objects from arbitrary SQL, in find_by_sql. This loses essentially all of the high level functionality of the find method; most notably, it isn&#8217;t possible to use find_by_sql results to instantiate an object graph, rather than a flat array of objects (similar to the eager loading feature in the regular find method).</p>
<p>ActiveRecord has fairly good high-level schema creation functionality (&#8220;migrations&#8221;). Though it lacks concepts for all but the basic database objects, support can be added for <a href="http://www.redhillonrails.org/#foreign_key_migrations">foreign key constraints</a> (I kid you not, they aren&#8217;t supported by Rails itself!) and <a href="http://activewarehouse.rubyforge.org/rails_sql_views/">views</a>. There&#8217;s also a simple way to execute arbitrary SQL. Migrations aren&#8217;t technically that amazing, but rather they&#8217;re a helpful organizational approach to what can be a really hairy problem: defining a schema and then applying changes to live databases while keeping track of what changes you&#8217;ve already applied.</p>
<p>Finally, there is a test data loading facility called Fixtures. The common opinion of Fixtures seems to be that they are broken by design and should be avoided. The main issue I&#8217;ve found with them is that the implementation ignores the kind of database design elements that any book on SQL would recommend, such as foreign keys and check constraints. I managed to circumvent this with a combination of a plugin and some customization, described in detail in my previous post, <a href="http://www.pervasivecode.com/blog/2007/08/02/rails-fixtures-the-test-db-and-testunit/">Rails, Fixtures, the Test DB, and Test::Unit</a>. With those changes, all test fixture data is preloaded in the right order (so constraints aren&#8217;t violated) before any tests run, and any data alterations within tests are rolled back automatically by Rails.</p>
<p>A secondary issue with Fixtures is that they go directly from YAML text files to SQL INSERT statements, bypassing the ActiveRecord Model classes. ActiveRecord does pretty much rule out any fancy mapping between database tables and objects, so that&#8217;s not a problem, but this model-skipping fixture loading implementation means that any code in your model object (validations, before_save filters, etc.) <i>will not be executed</i> when loading fixtures. So fixtures do not work well with the otherwise pervasive Rails design rule of &#8220;put all the intelligence in the application&#8221;.</p>
<p>Still, despite the commonly-held disdain for using fixtures at all, I find that they can be tamed. In fact I&#8217;ve even created a base data facility for loading the fundamental data set that needs to be in the live database (e.g. initial admin user info). My approach is basically to alter fixture behavior to treat it as essentially a bulk data loading tool, and to do the extra housekeeping after loading to make up for the fact that the ActiveRecord model code was bypassed.</p>
<p>As far as I know, there is no bulk data dumping functionality in Rails.</p>
<p>So, to summarize, of the five main ORM features, here&#8217;s how ActiveRecord stacks up:</p>
<ol>
<li><b>Describing Relationships</b>: Easy to understand and use, with lots of slick functionality</li>
<li><b>Querying</b>: Easy to understand and use, but limited to simple join structures, and not possible to customize query building or rewrite SQL before execution</li>
<li><b>Storage and Retrieval</b>: Very easy to use, but only within the limits of the query builder&#8217;s features</li>
<li><b>Schema manipulation</b>: Easy to understand and use; limited in functionality but readily extensible; solid third party plugins are available for missing schema objects</li>
<li><b>Bulk Loading and Dumping</b>: Loading is badly designed and implemented, but fixable with some effort; dumping is not offered</li>
</ol>
<p>Okay, so it definitely makes the easy stuff easy. But what about the rest?</p>
<p>As I observed before, ActiveRecord is not designed as a set of modules that you use to assemble a solution that fits your needs. That&#8217;s more of the Java approach to design, and it trades flexibility for convenience. It can be a major pain to assemble a working system out of all of those abstract Java APIs, which are sometimes so comically over-<a href="http://en.wikipedia.org/wiki/Design_Patterns">pattern</a>ized as to draw mockery such as the hilarious &#8220;Are Javalanders Happy?&#8221; code snippet from <a href="http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html">Execution in the Kingdom of Nouns</a>.  Rails makes the opposite trade-off: sacrifice flexibility and gain a very approachable API.</p>
<p>Unfortunately, the Java approach (too abstract to readily use, but extremely flexible) is easily wrapped with a simpler, more convenient, less customizable API. The Rails approach isn&#8217;t internally componentized (have a look at ActiveRecord&#8217;s activerecord/base.rb source file in its 2,165-line glory, almost all of which is one class), so if you want to fiddle with its internal behavior, you can&#8217;t. So with Rails, it&#8217;s all or nothing: high level slickness for simple requirements, or hand-written SQL and hand-coded results mapping for your complex requirements.</p>
<p>As I said at the beginning, though, the key question is not how comprehensive the high level feature set is. More important is the question of how painful things are when you drop down to a lower level for a greater degree of control.</p>
<p>It would be nice if there were a middle level of complexity, between the high-level &#8216;find&#8217; method and &#8216;has_xxx&#8217; associations, and raw SQL. There isn&#8217;t. I think that the reason there isn&#8217;t one is that there is still a persistent belief among many Rails core team members and community members that databases should be stupid: just a persistent hash. Once upon a time I worked that way myself: I didn&#8217;t have access to or skill with a SQL RDBMS, and so I solved all of my persistence problems with DBM files, which (using Perl&#8217;s Tie::Hash class) are conceptually just persistent hashtables. miniSQL was little more than a SQL query parser on top of that sort of storage engine, and MySQL originally was pretty similar. But big databases have all sorts of useful features that address complicated persistence requirements in a fairly elegant way.</p>
<p>Given that Ruby fans like the idea of domain specific languages, which let you work in a super high level language customized to the problem domain, it&#8217;s surprising that Rails groupthink is that SQL is bad. It&#8217;s actually a very high level language, and allows a well written database to do some pretty amazing optimization on the fly because it provides a strong layer of abstraction between what you requested and how the storage engine provides it.</p>
<p>No, it&#8217;s not dynamic, nor is it pure relational perfection, but it&#8217;s pretty darn good. Pre- and post-event validations and arbitrary callbacks to user-specified code, functions providing behavior on top of data&#8230; these are all things that Ruby and Rails fans hold in high regard when provided by Ruby and Rails, but which are considered a bad idea at the database layer. As I discussed at length in <a href="http://www.pervasivecode.com/blog/2007/08/02/rails-and-the-notion-of-stupid-databases-being-a-good-idea/">Rails and the notion of Stupid Databases Being a Good Idea</a>, this is a philosophy rooted in <a href="http://c2.com/cgi/wiki?DontRepeatYourself">DRY</a>, but it has some major flaws.</p>
<p>Mainly, there is the issue that some things <i>must</i> be done in the data tier, and trying to put them in the application tier doesn&#8217;t work. The best example that comes to mind is full text search. Satisfying queries is the database&#8217;s job, period. It&#8217;s just hideously slow to try and do an inner join in the application across a network link to a database. If you find yourself doing this, that&#8217;s a pretty good sign that your architecture is broken. But some queries are too complicated for ActiveRecord, so sometimes you must choose between a series of high level queries whose results are intersected in application code (easy to understand, but extremely inefficient), or hand coded SQL.</p>
<p>Well, SQL is fast and is a high level domain-specific language, so it isn&#8217;t actually a bad tool for the job. The problem is that this approach (the trapdoor to the lower level API) is regarded differently by different people. Some see it as a common and reasonable approach to complex requirements; others see it as a bad evil scary thing that should be avoided at all costs, a <a href="http://gilesbowkett.blogspot.com/2007/05/evan-weavers-railsconf-presentation.html">kludge and a design mistake</a>.</p>
<p>As a result, the low level option in Rails is anemic. It&#8217;s there, but you&#8217;re not supposed to use it. <a href="http://rlucas.net/blog/bugfix/ruby_active_record_makes_raw_sql_a_royal_pain.html">Ruby&#8217;s ActiveRecord Makes Dropping to Raw SQL a Royal Pain (Probably on Purpose)</a> notes that there are no bind variables allowed in ActiveRecord. You may be saying, &#8220;No, wait a minute, I&#8217;ve used them, that can&#8217;t be right.&#8221; That&#8217;s what I thought. Look at the source; the bind variable functionality is actually a high level feature built on top of drivers that don&#8217;t have that feature. Whatever you did at the high level, it&#8217;s going to the driver as a single string. Okay, it&#8217;s nice that they added that feature, especially since it provides a single point of testing and verification for safe escaping. But that functionality (in sanitize_sql) is <i>not part of the public API</i>. Fortunately <a href="http://rlucas.net/blog/bugfix/ruby_active_record_makes_raw_sql_a_royal_pain.html">that same article</a> provides a workaround that makes sanitize_sql accessible, so you can use bind variables in your hand coded SQL code, and pretend that the driver supports them. But that&#8217;s not likely to work forever.</p>
<p>The key problem with ActiveRecord is its least common denominator feature set, based around the least featureful of all popular SQL databases: MySQL. Years ago, MySQL AB (the vendor of the MySQL database) took a strong philosophical stand against pretty much any advanced database features (which their product lacked, and which competing products had), but lately they&#8217;ve softened and added those features that they claimed nobody really needed. In the meantime, Rails has been designed with minimal expectations for database sophistication; therefore, the limited functionality of ActiveRecord is fairly complete, assuming you&#8217;re using a database with similarly limited functionality.</p>
<p>Triggers, stored procedures, functions, data integrity constraints, nested transactions, and views are all examples of unsupported database functionality. Try and use them via ActiveRecord&#8217;s high level API, and you will quickly see how fragile and inflexible ActiveRecord really is. If you shouldn&#8217;t need those features in your database, then you shouldn&#8217;t need anything that ActiveRecord doesn&#8217;t already provide, so it shouldn&#8217;t matter that you can&#8217;t extend ActiveRecord.</p>
<p>Truly, these are features that you need only in a few small cases in your application, so looking at individual queries they&#8217;re needed rarely (which is not the same thing as &#8220;never&#8221;). But looking at whether you need one or more of them in a given <i>application</i>, they&#8217;re needed <i>more often than not</i>. The pain of using hand coded SQL makes this worse: some tricky things could be done either using a view or stored procedure, or using a really slick dynamic SQL statement. Making all of those options painful means that even a clever developer can&#8217;t use anything in their bag of tricks to craft an elegant solution.</p>
<p>Unfortunately, non-trivial web applications need things like full text search, complex associations between persistent objects, non-trival summary information about associated objects, and complex reports, and ActiveRecord fails at all of these. These are not just things that big dumb ancient companies that like using Object COBOL think they need; Amazon and eBay need them too.</p>
<p>The <a href="http://code.google.com/p/acts-as-tsearch/">acts_as_tsearch</a> plugin is a good case study of ActiveRecord&#8217;s design flaws. <a href="http://www.sai.msu.su/~megera/postgres/gist/tsearch/V2/">TSearch2</a> is the standard PostgreSQL full text search engine, and it&#8217;s pretty good in my opinion. It&#8217;s also pretty straightforward to use. Unfortunately for developers using Rails, TSearch2 uses SQL functions (mainly <code>to_tsquery</code> and <code>rank_cd</code>). The acts_as_tsearch plugin tries to inject SQL into ActiveRecord&#8217;s queries via the high-level <code>find</code> interface, but ultimately fails as soon as you use the :joins or :include options. The problem is that ActiveRecord has a very simplistic idea of how queries and joins work, and so if you need to inject SQL functions to get the job done (as is necessary in TSearch2 queries), too bad. (See also issues <a href="http://code.google.com/p/acts-as-tsearch/issues/detail?id=7">7</a> and <a href="http://code.google.com/p/acts-as-tsearch/issues/detail?id=8">8</a> in acts_as_tsearch, in which I describe and attempt to clean up the mess that results when you use find_by_tsearch in non-trivial ways.)</p>
<p>A fellow Rails developer asked me in all seriousness why I wasn&#8217;t abandoning the full text search functionality of TSearch2 and just using a completely separate, redundant database product designed exclusively for full text search. Seriously, that is considered the &#8220;easy&#8221; approach: one database for full text search, and another for ACID/OLTP/CRUD. Honestly if I were going to go down that road I would try hard to just abandon the SQL RDMBS and put everything in the other database, since Lucene and its imitators are capable of far more than just find-text-in-document queries. The pain of duplicating everything, using two query languages, two document representations (in addition to the object representation in Ruby) and writing application-tier query correlation makes the double-DB approach seem very unwise.</p>
<p>It makes far more sense to me to use the SQL RDMBS&#8217;s full text search facility, even if there&#8217;s a 2x or 3x read performance penalty, because the conceptual simplicity of having one powerful storage tier (instead of two halves cobbled together) eliminates a ton of ugliness in the application, and the SQL RDBMS is going to get clustered for reads anyway. Nevertheless, even if I&#8217;m wrong about this case (putting search in the SQL RDBMS instead of in a separate server), there are other cases for needing a smart database that gives you exactly the results you need and lets you push data logic into the data tier.</p>
<p>So, what do I suggest? Abandon Rails? Nope. I still like Ruby a lot, and find Rails very useful. I just think that ActiveRecord needs to support the low-level and middle-level abstractions better.</p>
<p>Specifically, supporting bind variables (either by exposing that sanitize_sql function, or better yet by making drivers and connection adapters support bind variables for real) would make the <code>find_by_sql</code>, <code>select_all</code>, and <code>exec</code> approaches to low-level SQL query execution less painful.</p>
<p>More difficult, and substantially more valuable, would be refactoring ActiveRecord::Base to split it up in the way I described above: association descriptions and unmarshalling code separate from query building code separate from SQL execution and result retrieval code. All of this could remain hidden for most users under the same old slick high-level API, but for advanced requirements, the ability to fiddle with the SQL and still use the built in high-level unmarshalling code to create object graphs from flat result sets would be very powerful, and useful. </p>
<p>I looked at one alternative to ActiveRecord, called <a href="http://sequel.rubyforge.org/">Sequel</a>, which overlaps with ActiveRecord only partially. It is a query builder and lazy result proxy, which is actually what I thought ActiveRecord would do when I first started working with Rails. The proxy design means that you can either keep adding constraints or start fetching results, from the same Dataset class. This seems like a pretty good approach, though I haven&#8217;t really looked closely to make sure it would fit what ActiveRecord needs.</p>
<p>What Sequel lacks, though, is the unmarshalling side: turning a 2-dimensional (rows of columns) result set into a complex object graph (customers with orders with order lines with products from suppliers stored in warehouses), with user-controlled eager or lazy loading behavior. Ruby is well-suited to a design that would allow user-specified code (i.e., a block) to decompose each row into the object graph associated with that row, leaving the remaining associations on those objects to be lazily provided via future queries.</p>
<p>So, I think there is hope for ActiveRecord, definitely. I considered the idea of rolling a minimal Hibernate clone, or some other sort of challenger to ActiveRecord, but I don&#8217;t that ActiveRecord is broken beyond repair. I think the shortest path to a badass Ruby ORM is through improvements (refactoring and abstraction) to ActiveRecord.</p>
<p>So, if you&#8217;ve read this far, you probably care about these issues. Here&#8217;s my call to action: Please help me make ActiveRecord less like VB and more like Delphi. Who else is interested in helping me with this effort? Are there alternatives that I&#8217;ve missed, or components that could be integrated into ActiveRecord to make it better?</p>
]]></content:encoded>
			<wfw:commentRss>http://www.pervasivecode.com/blog/2007/10/04/activerecord-the-visual-basic-of-object-relational-mappers/feed/</wfw:commentRss>
		<slash:comments>29</slash:comments>
		</item>
		<item>
		<title>Immature developer attitudes revealed in flames regarding CDBaby</title>
		<link>http://www.pervasivecode.com/blog/2007/09/23/immature-developer-attitudes-revealed-in-flames-regarding-cdbaby/</link>
		<comments>http://www.pervasivecode.com/blog/2007/09/23/immature-developer-attitudes-revealed-in-flames-regarding-cdbaby/#comments</comments>
		<pubDate>Sun, 23 Sep 2007 20:36:31 +0000</pubDate>
		<dc:creator>Jamie Flournoy</dc:creator>
				<category><![CDATA[architecture]]></category>
		<category><![CDATA[articles]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[process]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[ruby on rails]]></category>
		<category><![CDATA[sql]]></category>

		<guid isPermaLink="false">http://www.pervasivecode.com/blog/2007/09/23/immature-developer-attitudes-revealed-in-flames-regarding-cdbaby/</guid>
		<description><![CDATA[Derek Sivers of CDBaby kicks ass. He got a sophisticated and very very user-friendly, efficient, straightforward e-commerce system (including the back-end systems) written in PHP. Based on what I&#8217;ve read, he&#8217;s up there with Phil Greenspun in my opinion; that is, he&#8217;s among those who understand strategy and customer service and low-level technology and are [...]]]></description>
			<content:encoded><![CDATA[<p>Derek Sivers of CDBaby kicks ass. He got a sophisticated and very very user-friendly, efficient, straightforward e-commerce system (including the back-end systems) written in PHP. Based on what I&#8217;ve read, he&#8217;s up there with <a href="http://philip.greenspun.com/panda/">Phil Greenspun</a> in my opinion; that is, he&#8217;s among those who understand strategy and customer service and low-level technology and are able to build systems that don&#8217;t suck, resisting the temptation to be distracted by technological panaceas and fads. I may disagree with their individual technology decisions, but their higher-level thinking is excellent, so they&#8217;re definitely in the class of people who I&#8217;ll give the benefit of the doubt.</p>
<p>So when I read <a href="http://www.oreillynet.com/ruby/blog/2007/09/7_reasons_i_switched_back_to_p_1.html">7 reasons I switched back to PHP after 2 years on Rails</a> I was a bit surprised, but not much. He&#8217;s experienced with PHP (he says he&#8217;s written 90,000 lines of code for CDBaby!), and has a huge installed base of code he wrote and understands intimately. He tried Rails, it didn&#8217;t work the way he wanted, and he went back to PHP. It was immediately obvious to him that this was what he should continue using.</p>
<p>The most shrill and arrogant among the Rails community have been rather unkind, partly due to <a href="http://developers.slashdot.org/developers/07/09/23/1249235.shtml">this rather poorly written Slashdot headline</a> that misrepresents what Derek says in his article.<br />
<span id="more-45"></span><br />
I&#8217;m using Rails and I&#8217;m generally happy with it, though I have had to do some customization of the framework (mostly with pre-existing Rails plugins from like-minded developers)  to suit my style. At this time I plan to continue using Rails for my project, and to keep using it in future projects.</p>
<p>But, I thought it would be instructive to summarize the arguments made by the folks slamming Derek in the comments to his article.</p>
<ul>
<li>Rails is the correct solution for CDBaby, regardless of what the founder//designer/lead programmer of CDBaby thinks.</li>
<li>If you don&#8217;t agree with Rails&#8217; design, you&#8217;re wrong, and you need to change your design if not your whole business to fit Rails.</li>
<li>PHP is bad and can never result in good code. Conversely, Ruby is good and is always better than PHP.</li>
<li>Objects are better than SQL and tables. Domain specific languages are great, as long as they&#8217;re written in Ruby; using SQL as a domain specific language for data manipulation and querying is not Ruby and therefore is a bad idea. Also, just because one person can code something in PHP in 2 months that runs on one server, instead of two people taking over two years to do it in Rails (which is notoriously hardware-hungry), doesn&#8217;t make PHP + SQL better. Because objects make you more productive.</li>
<li>Just because you wrote a successful online store yourself from scratch, have an O&#8217;Reilly column, and then hired a Rails core team member who now works for the same company as most of the rest of the Rails core team including DHH (37Signals), doesn&#8217;t mean that you actually had people smart enough to rewrite your app in Rails. You need to prove to the world that what you wanted to do was impossible in Rails before we&#8217;ll give you permission to make technical decisions for yourself.</li>
<li>If you don&#8217;t like Rails it must be because Rails is too good for you. Maybe instead of learning from years of real world experience running a business and writing the entire software suite for that business yourself, and hiring one of the best Rails developers in the world for your transition to a new architecture, you should have gone back and gotten a CS degree.</li>
</ul>
<p>Since this is such an active flamewar with such sloppy readers (responding to the Slashdot headline&#8217;s misapprehension of what Derek wrote, instead of what he actually wrote), let me say this clearly:</p>
<p><strong>The above listed points are not my opinions. They are summaries of opinions I find immature and silly.</strong></p>
<p>(I&#8217;m assuming someone somewhere will read this post in anger and make themselves look foolish by arguing against these points as if I believed them anyway. I tried to warn ya&#8230;)</p>
<p>We can learn something from this. This same flamewar keeps appearing over and over and over, all over the internet, and before that, on BBSs and in print.</p>
<p>The simple fact of human mortality means that most of us are going to be learning and making mistakes that we imagine a wise, seasoned developer wouldn&#8217;t make. But that guy retired and is fishing, so it&#8217;s up to us to screw up and learn and hopefully do better next time.</p>
<p>For the last few thousand years, we&#8217;ve had the advantage of being able to read books, and get wisdom that way. But there are fads, and hyped books that seem to know it all. So you have to read a lot of books, and try a lot of contradictory ways of doing things, to get a mature enough perspective to make wise choices on future projects.</p>
<p>The point that is generally missed by everyone trying to do anything new to them, is that there&#8217;s a lot more out there that you don&#8217;t understand, and a lot of what you think is your brilliant new invention has been done before. The more you learn, the more humble you become as you learn that there are people WAY smarter that you are, and that there are people who have come before you who you will never catch up to.</p>
<p>In the case of web development, that means that there are people out there who disagree with you, and you might not actually know everything about everything like you think you do. Local experience and immersion in the problem they&#8217;re trying to solve makes them much better suited to solving their problem than some armchair quarterback. It&#8217;s tempting to fold your arms and feel smug about your quick dismissal of someone else who clearly isn&#8217;t as big a genius as you are, but the more certain you are of your own brilliance, the more likely it is that you&#8217;re just ignorant. (See also: <a href="http://gagne.homedns.org/~tgagne/contrib/unskilled.html">Unskilled And Unaware Of It</a>.)</p>
<p>In Derek&#8217;s case, he clearly jumped feet first into Rails and hired a rockstar developer, and then made the decision given a uniquely advantageous perspective that it just wasn&#8217;t working, after giving it a hell of a lot more time to pay off than I would have. (I&#8217;m not exactly sitting here scratching my head wondering &#8220;how could Derek have been so wrong?&#8221;)</p>
<p>What matters a lot more than choice of programming language is the ability to get the project done, meaning tested and correct and launched. Apparently for Derek, PHP is the way to get that done, and Rails ain&#8217;t.</p>
<p>Finally, consider the parallels between Derek in this case, and Phil Greenspun in his book (late 90&#8217;s). They both use SQL more than the &#8220;Objectistas&#8221; would like, which is to say, they use it directly and like it. They both use languages that are not the most fancy high level languages available, but they do completely understand how their application works, from UI all the way down to hardware performance, and they make integrated decisions that take business strategy and technological factors into consideration.</p>
<p>This is really critical to understand about why both of these guys are so smart: both of these people put aside dogma and made decisions that were all over the map, sometimes pragmatic and hackish, sometimes very rigorous and disciplined, depending on their assessment of the particular micro-issue they were addressing at the moment. They weren&#8217;t subscribing to any particular software development philosophy (<a href="http://www.laputan.org/mud/">Big Ball of Mud</a>, XP, RUP, UML, Agile whatever, waterfall, etc.), nor did they just choose the most hyped architecture of the day and blindly stick with it, but freely mixed and matched whatever they thought was appropriate given their perspective on the situation.</p>
<p>In other words, they didn&#8217;t fall into the trap of thinking that there is One True Way to do everything. They improvised. They integrated their own experience and perspective with the wisdom of others, and made the decision that worked for them.</p>
<p>I hate to sound like a moral relativist, because I&#8217;m not. But as a practitioner, I&#8217;m definitely becoming more and more of a process relativist every day. One of the best ways to make a project fail is to try and force-fit an architecture and/or a methodology onto that project without customizing them to the particular details of the project. <strong>The best methodology is called &#8220;Roll Your Own.&#8221;</strong></p>
<p>And so I leave you with this question, in the hope that it will help you with your current and future projects: <strong>Have you made any dogmatic decisions about your process or technology that are hurting your project?</strong></p>
<p>Maybe it&#8217;s smarter at this point to keep doing what you&#8217;re already doing, but maybe there are some things that you could change. Try to keep your <a href="http://wiki.jeffsandquist.com/default.aspx/GTD/MindLikeWater.html">Mind Like Water</a>, strong enough to cut through mountains but flexible enough to flow around obstacles, and making the decisions of which one to do based on the information that you and only you have.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.pervasivecode.com/blog/2007/09/23/immature-developer-attitudes-revealed-in-flames-regarding-cdbaby/feed/</wfw:commentRss>
		<slash:comments>29</slash:comments>
		</item>
		<item>
		<title>Making the Rails acts_as_tsearch plugin work with fixtures</title>
		<link>http://www.pervasivecode.com/blog/2007/09/22/making-the-rails-acts_as_tsearch-plugin-work-with-fixtures/</link>
		<comments>http://www.pervasivecode.com/blog/2007/09/22/making-the-rails-acts_as_tsearch-plugin-work-with-fixtures/#comments</comments>
		<pubDate>Sun, 23 Sep 2007 05:47:50 +0000</pubDate>
		<dc:creator>Jamie Flournoy</dc:creator>
				<category><![CDATA[postgresql]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[ruby on rails]]></category>
		<category><![CDATA[sql]]></category>

		<guid isPermaLink="false">http://www.pervasivecode.com/blog/2007/09/22/making-the-rails-acts_as_tsearch-plugin-work-with-fixtures/</guid>
		<description><![CDATA[acts-as-tsearch is pretty cool, except for the fact that it uses Ruby (app layer) instead of PL/pgSQL (DB layer) to update the tsvectors that are indexed for full text search. That means that fixture data gets inserted without being full text indexed. D&#8217;oh!
Here&#8217;s some code that changes that.

(I put this in my environment.rb because I&#8217;m [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://code.google.com/p/acts-as-tsearch/">acts-as-tsearch</a> is pretty cool, except for the fact that it uses Ruby (app layer) instead of PL/pgSQL (DB layer) to update the tsvectors that are indexed for full text search. That means that fixture data gets inserted without being full text indexed. D&#8217;oh!</p>
<p>Here&#8217;s some code that changes that.<br />
<span id="more-44"></span><br />
(I put this in my environment.rb because I&#8217;m not quite at the point of shoving all this into a plugin like I probably should.)</p>
<pre>
# make it so that fixture loading (test data and base data) includes the tsearch2 update_vector
class Fixtures
    class << self # we want to mess with self.instantiate_fixtures
        def create_fixtures_with_update_vector(fixtures_directory, table_names, class_names = {})
            create_fixtures_without_update_vector(fixtures_directory, table_names, class_names)
            # create a Class instance for each table name fixtures were loaded for, then call update_vector on it
            table_names.each do |tn|
                klass_name = (ActiveRecord::Base.pluralize_table_names ? tn.singularize.camelize : tn.camelize)
                begin
                    klass = Object.const_get(klass_name) # will fail if tn is a habtm table (no corresponding model class)
                    klass.update_vector if klass.respond_to?(:update_vector)
                rescue
                    nil # if it is a habtm table, there's no need to update a tsearch2 vector for sure
                end
            end
        end
        alias_method_chain :create_fixtures, :update_vector
    end
end
</pre>
<p>(Sorry for the formatting but I like wide lines in my source code and my WP theme doesn't. Just copy and paste and it should be fine.)</p>
]]></content:encoded>
			<wfw:commentRss>http://www.pervasivecode.com/blog/2007/09/22/making-the-rails-acts_as_tsearch-plugin-work-with-fixtures/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Bad, Bad Code</title>
		<link>http://www.pervasivecode.com/blog/2007/08/04/bad-bad-code/</link>
		<comments>http://www.pervasivecode.com/blog/2007/08/04/bad-bad-code/#comments</comments>
		<pubDate>Sat, 04 Aug 2007 22:21:02 +0000</pubDate>
		<dc:creator>Jamie Flournoy</dc:creator>
				<category><![CDATA[humor]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[labor]]></category>
		<category><![CDATA[management]]></category>
		<category><![CDATA[offshoring]]></category>
		<category><![CDATA[outsourcing]]></category>
		<category><![CDATA[sql]]></category>
		<category><![CDATA[testing]]></category>

		<guid isPermaLink="false">http://www.pervasivecode.com/blog/2007/08/04/bad-bad-code/</guid>
		<description><![CDATA[I&#8217;ve written before about tips for offshoring. One specific thing I said to watch for is the bait-and-switch of talent: during the sales process you&#8217;re shown rockstars, but the real code you get is written by clueless newbies. When you set up a project such that you&#8217;ve minimized the cost per hour of development, but [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve written before about <a href="http://www.pervasivecode.com/blog/2007/02/20/tips-for-offshoring/">tips for offshoring</a>. One specific thing I said to watch for is the bait-and-switch of talent: during the sales process you&#8217;re shown rockstars, but the real code you get is written by clueless newbies. When you set up a project such that you&#8217;ve minimized the cost per hour of development, but you don&#8217;t have anyone checking the work product (i.e. code reviews) coming from the subcontractor, very bad things happen.<br />
<span id="more-40"></span><br />
Here&#8217;s a doozy: <a href="http://www.discursive.com/blog/2007/07/in-2007-people-are-still-writing-jsp.html">In 2007, people are still writing JSP like this&#8230;</a></p>
<p>Check out the 4th message in the thread, with the big code sample.</p>
<ul>
<li>Table based HTML layout, and no CSS at all? Check. Heck, the table width % values don&#8217;t even add up to 100%.</li>
<li>SQL in the JSP? Check.</li>
<li>Making a new JDBC connection for each page view, instead of using a connection pool? Check.</li>
<li>Unescaped strings in the SQL? Check. (Not strings coming from the browser in this particular JSP page, but you don&#8217;t know where those strings originate. Why wouldn&#8217;t you escape it just in case?)</li>
<li>Failing to use a prepared statement? Check. (That would also solve the escaping problem.)</li>
<li>Using a string literal in a SQL or command line context, so you can&#8217;t log it beforehand? Check. Even better, the code makes a query string first, prints it (commented out), and then <i>uses a different string in the actual query</i>. Nice!</li>
<li>Using the JdbcOdbc driver? Check. (From <a href="http://java.sun.com/docs/books/tutorial/jdbc/basics/connecting.html">Sun&#8217;s JDBC Basics</a>: The JDBC-ODBC Bridge driver provided with JDBC is recommended only for development and testing, or when no other alternative is available.) I&#8217;m guessing that the use of ODBC here is the only reason why the database username and password aren&#8217;t embedded in the code sample and posted for all to see.</li>
<li>Empty exception catch block? Check.</li>
</ul>
<p>I&#8217;d have a hard time coming up with a fake example of bad code that was worse.</p>
<p>But wait, <a href="http://forum.java.sun.com/profile.jspa?userID=908177">what else has this person asked about</a>?</p>
<p>No way. Yes! <a href="http://forum.java.sun.com/thread.jspa?threadID=5156702&#038;messageID=9593464#9593464">Error in Socket and File Writing</a>!<br />
The post includes the router&#8217;s username and password, and its configuration including:</p>
<ul>
<li>Its IP address, and all of the routes it contains, and all of its interfaces and where they go</li>
<li>A couple of other passwords stored in the router</li>
<li>A crypto key that appears to be to a VPN (looks like a pre-shared key, meaning not a public key but one that must be kept secret)!</li>
</ul>
<p>I&#8217;m not gonna say &#8220;you get what you pay for&#8221; since open source software has served me very well, but I will say that you get what you bargain for. If your bargain includes not looking at the work product of the people you hire, which is to say, hiring the cheapest people available and not supervising them, you&#8217;re not going to be happy with what you get.</p>
<p>Of course, this <em>could</em> have been written by a U.S. citizen who works in a cube on-site and makes $200/hour. Point is, hire carefully, and supervise your workers. It seems simple when put that way, but it&#8217;s amazing how often companies are willing to hire software subcontractors carelessly (solely on price?) and then pay little or no attention to the resulting work, when the arrangement involves offshore outsourcing.</p>
<p>By the way, the IP addresses in the original post (with the awful code sample) are listed next to the name &#8220;Areva&#8221;, implying that this code is part of a project for Areva. Who is Areva? <a href="http://www.areva.com/servlet/operations-en.html">They make nuclear power plants</a>. Sweet dreams!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.pervasivecode.com/blog/2007/08/04/bad-bad-code/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Rails, Fixtures, the Test DB, and Test::Unit</title>
		<link>http://www.pervasivecode.com/blog/2007/08/02/rails-fixtures-the-test-db-and-testunit/</link>
		<comments>http://www.pervasivecode.com/blog/2007/08/02/rails-fixtures-the-test-db-and-testunit/#comments</comments>
		<pubDate>Fri, 03 Aug 2007 02:37:22 +0000</pubDate>
		<dc:creator>Jamie Flournoy</dc:creator>
				<category><![CDATA[architecture]]></category>
		<category><![CDATA[databases]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[postgresql]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[ruby on rails]]></category>
		<category><![CDATA[sql]]></category>
		<category><![CDATA[testing]]></category>

		<guid isPermaLink="false">http://www.pervasivecode.com/blog/2007/08/02/rails-fixtures-the-test-db-and-testunit/</guid>
		<description><![CDATA[From what I&#8217;ve seen, Rails&#8217; weakest features lie in the way it prepares the test database and test data, and Ruby&#8217;s Test::Unit isn&#8217;t much better than the awful but ubuiquitous JUnit that Java developers are accustomed to. I set out this week to impose my preferences on Rails in this area, and that took some [...]]]></description>
			<content:encoded><![CDATA[<p>From what I&#8217;ve seen, Rails&#8217; weakest features lie in the way it prepares the test database and test data, and Ruby&#8217;s Test::Unit isn&#8217;t much better than the awful but ubuiquitous <a href="http://www.junit.org/index.htm">JUnit</a> that Java developers are accustomed to. I set out this week to impose my preferences on Rails in this area, and that took some effort. Here&#8217;s what I did.<br />
<span id="more-39"></span></p>
<p>When I&#8217;ve implemented (in Java) what Rails does for database preparation, I did it like this:</p>
<ol>
<li>Create the test database exactly the same way that the developers&#8217; databases are created: by running the exact same code, pointed at a different database.</li>
<li>Load the appropriate sets of data for the test database. &#8220;Sets&#8221; is plural on purpose; most non-trivial databases include code tables, which constitute base data which are essentially part of the database design itself. Then, test code will want a fixed set of known test data to act upon, so that tests can measure whether the code did the right thing given the test data (the right inputs yield the right outputs).</li>
<li>Run the individual tests, providing some way of assuring that changes to the test data are undone before the next test.
</ol>
<p>At first (11 years ago) I used a hand-maintained SQL DDL file to create the databases. Later I split that up into one file per table, and made a list of the proper ordering of tables during creation (reversible for deletion). Later still, with <a href="http://www.hibernate.org/">Hibernate</a>, I ditched the DDL and let a higher-level ORM description of the table do the schema generation (which was painful in Hibernate since it wasn&#8217;t made to do that except from the command line, but it was possible to hack it into a state of relative beauty). The test data was always loaded from a bunch of text files that were easy to hand-edit (as opposed to a bunch of SQL INSERT statements).</p>
<p>Running the test with assurance of pristine test data was more or less horrific in a J2EE+Hibernate 2.x environment. The design of Hibernate and JUnit made it difficult to wrap tests in transactions, and the version of MySQL that we were using had no transactional storage engines available at all (MyISAM? Thanks, Red Hat!), so I ended up falling back on an intrusive but relatively high-performance design that required tests to declare if they were going to alter the test data, so that the test teardown method knew it had to reload the test data. Since we were waiting for Hibernate 3.0, MySQL 5.x, and a few other things to become part of our architecture, I left that solution in place and ended up moving on to a new job before fixing it.</p>
<p>Rails initially seemed to nail this problem: the test database is automatically made based on the development database; the data is loaded from YAML files called Fixtures, which feature a very simple and straightforward API, and tests run inside individual transactions. Nice!</p>
<p>Except not. Fixtures are loaded by specifying the tables for which you need test data loaded, and this is done in each Test::Unit::TestCase class, of which I have several hundred. They are stupidly reloaded each time you say a given TestCase is going to use them. Worse, the tables you&#8217;re using for this TestCase are emptied out using SQL DELETE statements, but if there is test data in other tables that has foreign key dependencies on the data being deleted, fixture loading will fail. (Rails was not designed for FKs to be enabled in the database, so encountering this this bug is a side effect of enabling them via the plugin.) This deletion behavior is pointless in light of transactions wrapping each test, but if you&#8217;re using MySQL MyISAM you can&#8217;t use transactions, so it needs to be there for people using MyISAM, which is to say, crazy people who care not for their data.</p>
<p>Since Test::Unit, like Java&#8217;s JUnit, lacks a hook for the beginning or end of a given TestCase class&#8217;s set of tests, there&#8217;s no way to accumulate a list of fixtures created and then delete them and/or reload them at the end. That would at least allow you to undo the creation of the fixtures so that the tables were all empty before the next set of fixtures were loaded. Sadly, Test::Unit is not that clever.</p>
<p>I initially fixed this problem a couple of months ago, using a hack that simply refuses to delete and re-create (test data) fixtures if they&#8217;re already loaded. That works since the fixture data progressively accumulates and is always clean since changes within tests are rolled back at the end of those tests.</p>
<p>Upon adding a trigger to a Rails migration and then writing a test case that checked to see if it was working, I found the true ugliness. Rails has <a href="http://wiki.rubyonrails.org/rails/pages/UnderstandingMigrations">Migrations</a>, which in my opinion are an excellent feature that works well, and is a more useful generalization of my ordered-list-o-tables and set of table-definition text files. But&#8230; when creating the test database, Rails uses the <a href="http://caboo.se/doc/classes/ActiveRecord/SchemaDumper.html">SchemaDumper</a>&#8217;s schema.rb output to create it, instead of using migrations. Talk about principle of least astonishment&#8230; I was pretty astonished. We have migrations, which is how we create databases! Great! So let&#8217;s use this other thing instead.</p>
<p>Also, SchemaDumper does not in fact dump the schema; it dumps tables and indices only. The RedHillOnRails foreign keys core plugin adds foreign key dumping to this output, but forget about check constraints, triggers, and stored procedures. Those schema objects are ignored, so your test database is not the same as your development (or production) database. Whoops.</p>
<p>I thought of about a dozen ways to deal with this:</p>
<ol>
<li>Abandon triggers and do it all in Rails, make a TODO to fix this later, and get on with feature implementation</li>
<li>Add code to the tests to check for the missing schema objects and add them if missing (eww)</li>
<li>Replace the db:test:prepare Rake task with one that tells PostgreSQL to copy the database as-is</li>
<li>Replace the db:test:prepare Rake task with one that tells PostgreSQL to use <a href="http://www.postgresql.org/docs/8.2/static/app-pgdump.html">pg_dump</a> instead of ActiveRecord::SchemaDumper</li>
<li>Hack the PostgreSQL-specific code that SchemaDumper uses to look at the <a href="http://www.postgresql.org/docs/8.2/static/catalog-pg-proc.html">pg_proc</a> and <a href="http://www.postgresql.org/docs/8.2/static/catalog-pg-trigger.html">pg_trigger</a> system catalogs and use code similar to the RedHillOnRails Core plugin to dump stored procs and triggers into schema.rb also</li>
<li>Just dump using pg_dump into a temp file and parse the output and add that to schema.rb (ewwwwwww)</li>
</ol>
<p>etc. etc.</p>
<p>I finally found the <a href="http://www.elctech.com/2007/7/12/migrate_test_db">Migrate Test DB Rake Plugin</a> which simply uses your Rails Migrations to create the test database. Lovely. Except I now had some new problems.</p>
<ol>
<li>rake db:schema:purge for PostgreSQL does dropdb/createdb on the test database to empty it out. That creates a database with no built in procedural langauges, so stored procs won&#8217;t work. Adding the language to that database is a DB superuser task, so it couldn&#8217;t be done inside of Rake. Fortunately I found that I could solve this via &#8220;createlang plpgsql template1&#8243; which puts plpgsql in the template database used for creating new databases. Easy.</li>
<li>My never-delete-fixtures code got into a fight with my base-data-loader code. They both used Fixtures to load data, and so the base data fixtures made the never-delete-fixtures code think that the test data was already in. So the tests failed due to lacking test data.</li>
</ol>
<p>I fixed this initially by modifying my BaseDataLoader class to not load base data if RAILS_ENV is &#8216;test&#8217;, and added code to the Migrate Test DB Plugin to set RAILS_ENV to &#8216;test&#8217; right before running the migrations on the test database. This is a workaround, really, because it still leaves the base data either missing entirely, or duplicated.</p>
<p>Then I switched to the <a href="http://www.elctech.com/2007/5/31/preloading-fixtures">Preload Fixtures plugin</a> which is nice but still leads to FK related errors. It grab the fixture names from your test/fixtures directory and loads all the files it finds, in the order it found them. That fails since alphabetical order and the required table creation order are different in my case.</p>
<p>Fortunately since I&#8217;m using the Migrate Test DB Plugin I can just observe the order in which tables were created and tell the Preload Fixtures plugin to do its work in the same order. This is in my environment.rb because that&#8217;s where all my project-wide monkeypatching currently lives. (Cleaning that up and maybe plugin-izing it is a TODO for the future.)</p>
<pre>
# Due to FKs, gotta specify ordering of fixture preloading here. Why not let migration create_table statements do it?
# (depends on Migrate Test DB Plugin being present; is here for the benefit of the preload_fixtures plugin)
module ActiveRecord::ConnectionAdapters::SchemaStatements
    alias create_table_orig create_table
    def create_table(table_name, options = {}, &#038;block)
        fixture_filename = "#{table_name}.yml"
        if File.file?(File.join([RAILS_ROOT, 'test', 'fixtures' ,fixture_filename]))
            ENV['FIXTURES'] = [ENV['FIXTURES'], fixture_filename].compact.join(',')
            # puts ENV['FIXTURES']
        end
        create_table_orig(table_name, options, &#038;block)
    end
end
</pre>
<p>Sadly if you run &#8220;rake test&#8221; it runs ruby as a subprocess in order to do &#8220;rake test:units&#8221;, &#8220;rake test:functionals&#8221;, and &#8220;rake test:integration&#8221;. That means that the migrations are run once (before the tests), but that the preloading is done three times. The second and third times through, though, the preloading fails since it&#8217;s trying to delete-then-create each table&#8217;s fixtures in table-creation order. So, a patch to preload_fixtures.rb is needed, to ensure that deletes are done first, in the reverse order of table creation. Here&#8217;s what the new preload! method looks like:</p>
<pre>
  def self.preload!
    puts "PRELOADING FIXTURES..."

    require 'active_record/fixtures'
    ActiveRecord::Base.establish_connection(:test)
    fixture_filenames = (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'test', 'fixtures', '*.{yml,csv}')))

    # delete first, in reverse order
    fixture_filenames.reverse.each do |fixture_file|
        table_name = File.basename(fixture_file, '.*') # hack; might not be correct if class name != camelized table name
        ActiveRecord::Base.connection.delete "DELETE FROM #{table_name}", 'Fixture Delete'
    end

    fixture_filenames.each do |fixture_file|
      Fixtures.create_fixtures(File.join(RAILS_ROOT, 'test', 'fixtures'), File.basename(fixture_file, '.*'))
    end
    puts "DONE. Loaded #{Fixtures.all_loaded_fixtures.keys.length} fixtures."
  end
</pre>
<p>I&#8217;m not sure, but I think there&#8217;s an assumption in there that the table name is the same as the fixture name. My patch also makes that assumption, which is true in the case of my project. But in your project you might not have done that, so further hackery might be needed.</p>
<p>So, it all seems to work correctly now, and I&#8217;m back to working on my trigger code. If this seems like it took a lot of effort, it did, but I think it&#8217;ll be worth it once I start using stored procs and triggers more. That phase begins now.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.pervasivecode.com/blog/2007/08/02/rails-fixtures-the-test-db-and-testunit/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Rails and the notion of Stupid Databases Being a Good Idea</title>
		<link>http://www.pervasivecode.com/blog/2007/08/02/rails-and-the-notion-of-stupid-databases-being-a-good-idea/</link>
		<comments>http://www.pervasivecode.com/blog/2007/08/02/rails-and-the-notion-of-stupid-databases-being-a-good-idea/#comments</comments>
		<pubDate>Fri, 03 Aug 2007 02:16:40 +0000</pubDate>
		<dc:creator>Jamie Flournoy</dc:creator>
				<category><![CDATA[architecture]]></category>
		<category><![CDATA[databases]]></category>
		<category><![CDATA[postgresql]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[ruby on rails]]></category>
		<category><![CDATA[sql]]></category>

		<guid isPermaLink="false">http://www.pervasivecode.com/blog/2007/08/02/rails-and-the-notion-of-stupid-databases-being-a-good-idea/</guid>
		<description><![CDATA[For the last few days I&#8217;ve been struggling to bend Rails to my will regarding the proper way to assure data consistency. Today I made some progress. This builds upon some research I did a few months ago, and hopefully this is a more or less complete solution to the problem of making Rails work [...]]]></description>
			<content:encoded><![CDATA[<p>For the last few days I&#8217;ve been struggling to bend Rails to my will regarding the proper way to assure data consistency. Today I made some progress. This builds upon some research I did a few months ago, and hopefully this is a more or less complete solution to the problem of making Rails work the way I want it to regarding test databases.<br />
<span id="more-37"></span><br />
<a href="http://en.wikipedia.org/wiki/David_Heinemeier_Hansson">DHH</a> has clearly stated that <a href="http://web.archive.org/web/20060418215514/http://www.loudthinking.com/arc/000516.html">he does not like a smart database</a>. This is common among application developers, particularly in the agile methods camp, in that they generally appear not to understand relational set theory, or if they do, they believe that it is inherently inferior to object oriented methods (which lack a theoretical basis, as <a href="http://en.wikipedia.org/wiki/Fabian_Pascal">Fabian Pascal</a> will happily shout at anyone who will listen). I gather from DHH&#8217;s statements that he merely is trying to practice <a href="http://c2.com/cgi-bin/wiki?DontRepeatYourself">Don&#8217;t Repeat Yourself</a> (a.k.a. DRY, one of the most important values of Rails). I gather from Rails itself that he either respects the need of some folks to disagree with him enough to provide hooks to bypass ActiveRecord, or that he at least agreed with someone else&#8217;s patch. By this I mean that there are ways around the ORM features of ActiveRecord, to do raw SQL and to execute raw DDL at database creation time, which implies that he isn&#8217;t trying to force his opinions on others, but rather to make it easier to do things his way than to do them a different way.</p>
<p>Fair enough. Rails is opinionated software, as DHH often says, and I have found several cases where letting go of my particular way of doing things has been fine, given that Rails has a different but equally valid way of doing things that is made super easy by the framework. </p>
<p>However, I disagree with his decision to keep the DB stupid, for two reasons.</p>
<p>First, I prefer to put logic where it belongs, rather than gathering it all in one place. AJAX, and in particular Google Maps, is a good example of presentation logic going where it belongs, making the whole application work better. SQL RDBMSs have features that can be abused, and in some cases these features are there because a wrong-thinking but wealthy client demanded them, but most of the advanced features that a &#8220;Real Database&#8221; has are there so that you can protect yourself against data loss or data corruption. The database is in a unique position to let you declare rules for things that must always be true, and then to trust that the database will never violate those rules. Older versions of MySQL were notably lacking in these features and their absence was justified by MySQL staff who basically said &#8220;you don&#8217;t need that, and if you want it, you&#8217;re confused.&#8221; Rails has inherited some of these damaged assumptions from MySQL, leaving basic relational features like foreign keys out of the framework(!). Fortunately Rails allows plugins, and there is a set of <a href="http://www.redhillonrails.org/">foreign key plugins</a> that overturn this decision. But in general, if the database belongs to your application, that&#8217;s not an excuse to move database functionality into application code. By calling it your application&#8217;s database (as opposed to an <a href="http://martinfowler.com/bliki/DatabaseStyles.html">Integration Database</a>) you imply that it is part of your application, and therefore any rules or procedural code in it is necessarily also part of your application. You can&#8217;t monopolize the database and say that no one else has any business using it, while at the same time holding it at arm&#8217;s length and saying it&#8217;s not a valid part of the application. It is. No, business rules probably don&#8217;t belong in the database, but basic data consistency maintenance (in rule or procedural form) does.</p>
<p>I&#8217;m being charitable here, but my experience with individual practitioners of the Stupid Database Method invariably ends with me finding out that they don&#8217;t really understand databases at all (hence the desire to abstract the database away entirely with a driver plugin architecture topped by an ORM layer, lest they have to understand how a specific database product works), and would rather remain ignorant and reinvent the same functionality in the application layer or in the ORM layer. (It&#8217;s a case of &#8220;when all you have is a hammer, everything looks like a nail&#8221;, where the hammer is a general-purpose programming language, and you&#8217;re looking at a problem of high performance concurrent transactional programming.)</p>
<p>Not surprisingly, the database of choice for these folks is the least featureful, lowest cost, easiest to install one available. Because naturally it&#8217;s much more agile to write and debug new multithreaded transactional code in a high level dynamic language. than it is to get the same functionality for free in a thoroughly tested product that&#8217;s written in C. Right? Perhaps DHH is not one of these people. I assume he is not, again based on what he has said and coded. But nevertheless, the folks I&#8217;ve talked to personally who agree with his point of view are all coming from a point of view of willful ignorance.</p>
<p>Secondly, I prefer to employ defense in depth against data errors. Transient errors can have workarounds, but data errors are permanent, and that means that if your data is valuable, the damage done can be irreversible. Just because it&#8217;s possible for correct application code to avoid race conditions, improper escaping, etc. doesn&#8217;t mean that you should put all your eggs in that basket. When the price of data corruption is high (i.e. if you value the data in your database) then it&#8217;s worth the duplication of effort: test the application code, but also put a constraint in the database that will catch things the application code missed.</p>
<p>This is the same sort of thinking that leads to using automated unit tests, then functional tests, then integration tests, and then some manual QA, all overlapping. Duplication of effort? Yes. Worth it? Yes. Database bugs are arguably the worst kind of bugs to find in production, so they merit extra code that maybe isn&#8217;t absolutely necessary for the application to work, but is nice to have since you&#8217;d like to sleep at night.</p>
<p>So, I feel justified in wanting to put CHECK constraints and triggers in my database.</p>
<p>The implementation details are discussed in <a href="/blog/2007/08/02/rails-fixtures-the-test-db-and-testunit/">part 2</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.pervasivecode.com/blog/2007/08/02/rails-and-the-notion-of-stupid-databases-being-a-good-idea/feed/</wfw:commentRss>
		<slash:comments>23</slash:comments>
		</item>
		<item>
		<title>Full Text Search refuses to be a black box</title>
		<link>http://www.pervasivecode.com/blog/2007/07/15/full-text-search-refuses-to-be-a-black-box/</link>
		<comments>http://www.pervasivecode.com/blog/2007/07/15/full-text-search-refuses-to-be-a-black-box/#comments</comments>
		<pubDate>Mon, 16 Jul 2007 02:48:56 +0000</pubDate>
		<dc:creator>Jamie Flournoy</dc:creator>
				<category><![CDATA[XML]]></category>
		<category><![CDATA[architecture]]></category>
		<category><![CDATA[databases]]></category>
		<category><![CDATA[postgresql]]></category>
		<category><![CDATA[ruby on rails]]></category>
		<category><![CDATA[sql]]></category>

		<guid isPermaLink="false">http://www.pervasivecode.com/blog/2007/07/15/full-text-search-refuses-to-be-a-black-box/</guid>
		<description><![CDATA[Once upon a time, before Google pwn3d internet search, there were several competing definitions for full text search. Altavista more or less gave you results matching the exact strings you gave it, but in a crazy order that made it painful to use. Excite (my favorite back then) used a dictionary to achieve stemming and [...]]]></description>
			<content:encoded><![CDATA[<p>Once upon a time, before Google pwn3d internet search, there were several competing definitions for full text search. Altavista more or less gave you results matching the exact strings you gave it, but in a crazy order that made it painful to use. Excite (my favorite back then) used a dictionary to achieve stemming and synonym matches: searching for &#8216;dogs&#8217; would also match documents that contained &#8216;canines&#8217; or &#8216;dog&#8217;. Then Google blew them all away, and established a dominant set of expectations for how text search behaves.</p>
<p>I forgot about this, which is why I&#8217;m frustrated by the almost ridiculous complexity of the major server-side text search engines available right now. But it makes sense, once you learn what the options are.<br />
<span id="more-35"></span><br />
The first thing you have to decide is if you want to stick with an in-database search feature, meaning the one that comes with your SQL RDBMS of choice. If so, you get <a href="http://en.wikipedia.org/wiki/ACID">ACID</a> and hopefully <a href="http://en.wikipedia.org/wiki/Multiversion_concurrency_control">MVCC</a> features, which is probably why you&#8217;re using a SQL RDBMS in the first place: you&#8217;d like to get back the data that you put into it, even with multiuser access.</p>
<p>This option is just becoming decent in the last few years; typically you pay a penalty for the ACID and MVCC assurance, as well as for the smaller audience that uses that particular database. The factory installed radio is never as good as the one you order from <a href="http://www.crutchfield.com/">Crutchfield</a>, and likewise, the bundled thingamajig is never as good as the best available special-purpose thingamajig. But you get a few things in return: examples specific to your chosen database (duh), a simplified storage architecture, and probably some lower resource requirements.</p>
<p>By &#8220;simplified storage architecture&#8221; I mean that you don&#8217;t have to add your data to the database, and then also to the search engine, nor do you have to query the search engine first, and then possibly use the results to query the database if the search engine doesn&#8217;t contain everything you need. By &#8220;probably lower resource requirements&#8221; I mean that you&#8217;re not running two redundant but slightly differently optimized database servers: one SQL RDBMS and one specialized full text search server. Two redundant servers means double the storage, double the ram, plus or minus some variation in overhead (row size in bytes vs. page size, etc.). The full text index built into the database product will need a substantial amount of extra space for the indexes in memory and on disk, but almost certainly this will be less than a separate search server would need for the index and data, plus its own code, in memory and on disk.</p>
<p>An argument could be made for dumping the SQL RDBMS in favor of just using the dedicated full text search engine, but given that SQL RDMBSs tend to be the only game in town that offers unlimited ad-hoc querying, ACID, MVCC, and easy integration with all sorts of application languages and frameworks, I suspect that the folks making that argument either have really unusual requirements in mind, or are bozos who fall in love with architectures that work well for a few of their requirements and suck utterly at the remaining requirements.</p>
<p>There are quite a lot of those people out there. The same sort of suggestion has been made for object databases and XML databases in the past, and it turns out that the ones that blow away SQL RDBMSs in performance also lack one or more of the qualities that make SQL RDBMSs so useful. Putting those features back in tends to bring performance back down to a level close to that of the SQL RDBMS, and the argument for total replacement becomes weak. But the arguments, and arguers, remain.</p>
<p>But, perhaps your requirements for search performance are extreme: you need super duper fast search, which typically means you need a reasonable turnaround time on searches of a huge number of documents, with lots of users searching. The leading dedicated search engines lately seem to be <a href="http://en.wikipedia.org/wiki/Lucene">Lucene</a> (or one of its derivatives, such as <a href="http://ferret.davebalmain.com/trac/">Ferret</a>) or <a href="http://www.xapian.org/">Xapian</a>. In either case, you get super high performance, at the cost of some additional resources due to overlap with the SQL RDBMS, application complexity (two reads, two writes), and sysadmin complexity (a whole &#8216;nuther product to manage in order to keep the overall application up and running).</p>
<p>So, I&#8217;ll just accept that there are reasons to do either one, and maybe some corner cases in which just using a dedicated search engine makes sense instead of a SQL RDBMS. But let&#8217;s move on to the not-a-black-box aspect of full text search.</p>
<p>Here are the key issues:</p>
<ul>
<li>Full text search indices get huge quickly.</li>
<li>Some words are almost meaningless to searchers but are extremely commonly used.</li>
<li>Some words mean the same thing, or are variations of the same word.</li>
<li>Various kinds of coded character data (scientific notation, URLs, mailing addresses, etc.) are commonly embedded in searchable text.</li>
</ul>
<p>As a result of all of these, simply accelerating <code>"select * from cute_pet_stories WHERE UPPER(story) LIKE '%DOGS%'"</code> isn&#8217;t a viable approach. Instead, the search indexer requires additional information, such as the character encoding and language of the text being indexed, and uses that to simplify the text being indexed into root words (dog instead of dogs) that don&#8217;t include low-value words such as &#8220;a&#8221;, &#8220;the&#8221;, &#8220;of&#8221;, etc. (these are called <em>stop words</em>). Also, it may differentiate encoded data from regular language text, and handle it specially.</p>
<p>PostgreSQL includes a search engine called <a href="http://www.sai.msu.su/~megera/postgres/gist/tsearch/V2/">Tsearch2</a>, which is apparently <a href="http://people.planetpostgresql.org/xzilla/index.php?/archives/280-PostgreSQL-full-text-search-testing-PART-II.html">quite fast</a> if you&#8217;re willing to sacrifice size (big indexes) and write performance.</p>
<p>The implementation of a Tsearch2-indexed table is interesting: first you add a column to your table that&#8217;s just there to be indexed, and you fill it using text-mangling functions that do  stemming (dogs->dog), stop-word removal, and word counting. That leaves you with a column of type <code>tsvector</code>. Then you create an index on that column, and do your text queries against that index. You have to clean up your search text first, though, and similarly mangle it into an appropriately stemmed, stop-word-free <code>tsquery</code> object which itself can contain boolean expressions that will be used in the search process.</p>
<p>(There&#8217;s an <a href="http://code.google.com/p/acts-as-tsearch/">acts_as_tsearch</a> Rails plugin that attempts to simplify this into an idiom that makes more sense from a declarative standpoint. It looks pretty immature but I&#8217;m gonna give it a whirl anyway.)</p>
<p>Lucene does something similar, using Java classes it calls Analyzers to encapsulate the same kind of text-mangling behavior that Tsearch2 performs using its to_tsvector SQL function. Xapian <a href="http://www.xapian.org/docs/stemming.html">also has this same feature</a>, apparently from the <a href="http://snowball.tartarus.org/">same original source</a>. So the model of first preparing your text for indexability, then indexing it, then searching with similarly prepared query text, appears to be common if not universal.</p>
<p>Hopefully now you understand, as I now do, that full text search is inherently complicated, but not necessarily slow. All you need to do is understand the generic way that full text indexing and searching works, and then make a decision about integrated vs. standalone based on your setup.</p>
<p>I&#8217;m going to try Tsearch2 + acts_as_tsearch. I&#8217;ll let you know how it goes.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.pervasivecode.com/blog/2007/07/15/full-text-search-refuses-to-be-a-black-box/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

