Add "Limit Yourself to MySQL No More" presentation from php|works 2005.

This commit is contained in:
Dan Scott
2005-09-19 17:26:29 +00:00
parent 4b60fc3481
commit d2a0722f2d
21 changed files with 854 additions and 0 deletions

41
mysql-limits.xml Normal file
View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<presentation
template="css"
navsize="1.5em"
navmode="html"
navbarbackground="#4373b4"
navbartopiclinks="1"
navColor="#f1fbff"
logo1=""
stylesheet="presentations/slides/mysql-limits/yuck.css"
backgroundfixed="1" >
<topic>Databases</topic>
<title>LIMIT Yourself to MySQL No More</title>
<event>php|works 2005</event>
<location>Toronto, Ontario</location>
<date>September 17th, 2005</date>
<speaker>Dan Scott</speaker>
<email>dan@coffeecode.net</email>
<url>http://coffeecode.net/</url>
<slide>slides/mysql-limits/title.xml</slide>
<slide>slides/mysql-limits/intro.xml</slide>
<slide>slides/mysql-limits/why-not-mysql.xml</slide>
<slide>slides/mysql-limits/subject.xml</slide>
<slide>slides/mysql-limits/target.xml</slide>
<slide>slides/mysql-limits/opening.xml</slide>
<slide>slides/mysql-limits/intertwining.xml</slide>
<slide>slides/mysql-limits/limit.xml</slide>
<slide>slides/mysql-limits/counting.xml</slide>
<slide>slides/mysql-limits/reinventing.xml</slide>
<slide>slides/mysql-limits/abstraction.xml</slide>
<slide>slides/mysql-limits/abstraction-2.xml</slide>
<slide>slides/mysql-limits/schemas.xml</slide>
<slide>slides/mysql-limits/schemas-time.xml</slide>
<slide>slides/mysql-limits/schemas-indexes.xml</slide>
<slide>slides/mysql-limits/schemas-schemas.xml</slide>
<slide>slides/mysql-limits/wrapping-up.xml</slide>
<slide>slides/mysql-limits/resources.xml</slide>
<slide>slides/mysql-limits/thank-you.xml</slide>
</presentation>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<slide>
<title>Database abstractions (continued)</title>
<blurb>
Sure enough, taking a page at random, we can quickly find MySQL-specific SQL
being issued through the "abstraction" layer:
</blurb>
<example type="php"><![CDATA[// get the subset (based on limits) of required records
$query = "SELECT cd.*, cc.title AS category, u.name AS user, v.name as editor"
. "\n FROM #__contact_details AS cd"
. "\n LEFT JOIN #__categories AS cc ON cc.id = cd.catid"
. "\n LEFT JOIN #__users AS u ON u.id = cd.user_id"
. "\n LEFT JOIN #__users AS v ON v.id = cd.checked_out"
. $where
. "\n ORDER BY cd.catid, cd.ordering, cd.name ASC"
. "\n LIMIT $pageNav->limitstart, $pageNav->limit"
;
$database->setQuery( $query );
$rows = $database->loadObjectList();]]></example>
<blurb>
So there are some problems here from a portability perspective,
primarily in the hard-coded non-standard LIMIT clause.
It is not worth defining a database abstraction layer
for your application if you are going to issue SQL directly from within every
other file in your application.
</blurb>
<blurb>
Instead, consider defining a database abstraction with functions,
or classes and methods, for the functionality required by your application,
so that other databases can simply implement that interface.
Then a simple setting in the user's configuration
file will determine which database they want to use, and which implementation of the
application logic to invoke.
</blurb>
<blurb>For example, instead of defining %Myappdb::issueQuery()%, define %Myapp::getActiveEditors()% with the expected input parameters and expected return values, and let
the actual database-specific code implement that in the most efficient way.</blurb>
</slide>

View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<slide>
<title>Database abstractions</title>
<blurb>
Well, so far so good. But while FRED appears to be defining a database access
abstraction in %includes/database.php%, the implementation is hardcoded for
MySQL using the %mysql% extension, which somewhat defeats the purpose of
abstracting an interface. Let's take a quick look for our favourite MySQL keyword
to see where the real work is being done:
</blurb>
<example><![CDATA[daniels@dullard:~/phpworks> find . -name '*.php' -exec grep -Hil 'LIMIT' {} \;
./FREDts/content/geshi/geshi/xml.php
./FREDts/content/geshi/geshi/css.php
./FREDts/content/geshi/geshi/sql.php
./FREDts/content/geshi/geshi/php-brief.php
./FREDts/content/geshi/geshi/html4strict.php
./FREDts/content/geshi/geshi/javascript.php
./FREDts/content/geshi/geshi/php.php
./FREDts/content/geshi/geshi.php
./FREDts/content/mospaging.php
./components/com_rss/rss.php
./components/com_content/content.html.php
./components/com_content/content.php
./components/com_banners/banners.php
./components/com_newsfeeds/newsfeeds.html.php
./installation/install4.php
./modules/mod_latestnews.php
./modules/mod_banners.php
./modules/mod_sections.php
./modules/mod_archive.php
./modules/mod_mostread.php
./modules/mod_newsflash.php
./administrator/components/com_contact/admin.contact.php
./administrator/components/com_content/admin.content.php
./administrator/components/com_messages/admin.messages.html.php
./administrator/components/com_messages/admin.messages.php
./administrator/components/com_menumanager/admin.menumanager.html.php
./administrator/components/com_menumanager/admin.menumanager.php
./administrator/components/com_typedcontent/admin.typedcontent.php
./administrator/components/com_FREDts/admin.FREDts.php
./administrator/components/com_syndicate/admin.syndicate.php
./administrator/components/com_statistics/admin.statistics.html.php
./administrator/components/com_statistics/admin.statistics.php
./administrator/components/com_poll/admin.poll.php
./administrator/components/com_modules/admin.modules.php
./administrator/components/com_sections/admin.sections.php
./administrator/components/com_categories/admin.categories.php
./administrator/components/com_banners/admin.banners.html.php
./administrator/components/com_banners/admin.banners.php
./administrator/components/com_config/admin.config.php
./administrator/components/com_newsfeeds/admin.newsfeeds.php
./administrator/components/com_templates/admin.templates.php
./administrator/components/com_languages/admin.languages.php
./administrator/components/com_weblinks/admin.weblinks.php
./administrator/components/com_frontpage/admin.frontpage.php
./administrator/components/com_admin/admin.admin.html.php
./administrator/components/com_menus/admin.menus.php
./administrator/components/com_menus/admin.menus.html.php
./administrator/components/com_trash/admin.trash.php
./administrator/components/com_trash/admin.trash.html.php
./administrator/components/com_users/admin.users.html.php
./administrator/components/com_users/admin.users.php
./administrator/modules/mod_popular.php
./administrator/modules/mod_latest.php
./administrator/modules/mod_logged.php
./administrator/modules/mod_fullmenu.php
./administrator/modules/mod_components.php
./administrator/includes/pcl/pcltar.lib.php
./administrator/includes/pageNavigation.php
./index.php
./includes/frontend.html.php
./includes/Cache/Lite.php
./includes/domit/php_text_cache.php
./includes/gacl_api.class.php
./includes/sef.php
./includes/pathway.php
./includes/FRED.php
./includes/database.php
./includes/patTemplate/patTemplate.php
./includes/patTemplate/patTemplate/Modifier/Surround.php
./includes/phpmailer/class.smtp.php
./includes/pageNavigation.php
]]>
</example>
</slide>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<slide>
<title>Counting results of SELECT statement</title>
<blurb>A very popular MySQL idiom is to use the return value of %mysql_query()% to
reflect the number of rows that will be returned by the SELECT statement that was
issued.</blurb>
<list>
<bullet>But in most database APIs, the return value from executing a SELECT statement is simply true or false reflecting success or failure.</bullet>
<bullet>If your database supports scrollable cursors, and you request a scrollable cursor when you issue your SELECT statement, you *will* get an accurate count of the number of rows in the result set.</bullet>
<bullet>Most times, however, you can either issue a separate SELECT COUNT(*)
if you actually want the number of rows, or just try fetching the first row from the result set.</bullet>
<bullet>For small result sets, you can use %PDO::fetchAll()% to return all of the rows in the result set. The %ibm_db2% extension that Apache Derby requires will probably implement a %db2_fetch_all()% function in the near future to provide the equivalent functionality.</bullet>
<bullet>You can also step through the results of a forward-only cursor until the fetch call fails and increment a counter to find out how many rows are in the result set.</bullet>
</list>
</slide>

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<slide>
<title>Intertwining</title>
<blurb>
So let's look at the most obvious file in the list: %includes/database.php%:
</blurb>
<example><![CDATA[class database {
/* ... */
/**
* Database object constructor
* @param string Database host
* @param string Database user name
* @param string Database user password
* @param string Database name
* @param string Common prefix for all tables
*/
function database( $host='localhost', $user, $pass, $db, $table_prefix ) {
// perform a number of fatality checks, then die gracefully
if (!function_exists( 'mysql_connect' )) {
//or die( 'FATAL ERROR: MySQL support not available. Please check your configuration.' );
$mosSystemError = 1;
$basePath = dirname( __FILE__ );
include $basePath . '/../configuration.php';
include $basePath . '/../offline.php';
exit();
}]]></example>
<blurb>
Well, we immediately see the assumptions about MySQL being the one and only
database that FRED will ever connect to. One approach to opening up this file
structure to support multiple databases would be:
</blurb>
<list>
<bullet type="number">Turn %database.php% into a directory</bullet>
<bullet type="number">Include database-specific implementations inside that directory</bullet>
<bullet type="number">Load the right implementation based on a configuration directive</bullet>
</list>
<example><![CDATA[includes/database/adodb.php
includes/database/ibm_db2.php
includes/database/ibm_db2_derby.php
includes/database/mdb2.php
includes/database/mysql.php
includes/database/oci8.php
includes/database/pgsql.php]]></example>
</slide>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<slide>
<title>What I'm here to talk about</title>
<list>
<bullet>I'm sick...</bullet>
<bullet>I really enjoy working with databases</bullet>
<bullet class="indent">and PHP</bullet>
<bullet>but many PHP applications are written for MySQL only</bullet>
<bullet class="indent">and I want to run them on Apache Derby and DB2</bullet>
</list>
<blurb>So in this presentation I'm going to step through the process of
what would be required to convert a large PHP application from being
limited to running on MySQL to supporting any one of a number of databases,
without sacrificing performance, and without complicating the design^1^.
</blurb>
<blurb>
^1^ - ~too much~
</blurb>
</slide>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<slide>
<title>LIMITing success</title>
<blurb>
%includes/database.php% also gives us our first encounter with the LIMIT clause:
</blurb>
<example><![CDATA[function move( $dirn, $where='' ) {
$k = $this->_tbl_key;
$sql = "SELECT $this->_tbl_key, ordering FROM $this->_tbl";
if ($dirn < 0) {
$sql .= "\nWHERE ordering < $this->ordering";
$sql .= ($where ? "\n AND $where" : '');
$sql .= "\nORDER BY ordering DESC\nLIMIT 1";
} else if ($dirn > 0) {
$sql .= "\nWHERE ordering > $this->ordering";
$sql .= ($where ? "\n AND $where" : '');
$sql .= "\nORDER BY ordering\nLIMIT 1";
}]]>
</example>
<blurb>
In this case, it appears that the LIMIT clause is being used because the
application wants to prevent MySQL from returning every row in the result
set. Most other databases will simply return the first row when requested,
and nothing more, avoiding the whole problem of having to artificially
limit your result set.
</blurb>
<blurb>
If you do have a legitimate requirement for limiting the rows returned by
a query in this fashion, and your database does not support the LIMIT clause
(MySQL, PostgreSQL, SQLite do) there are a number of quasi-standard approaches
you can take depending on the database you are running against:
</blurb>
<list>
<bullet>Forward-only cursors: calling %db2_fetch_row()% or %PDO->fetch()% steps through the rows in the result set, without retrieving them,
until you reach the row(s) that you actually want to return. Fetching until the call fails will also let you count the approximate number of rows in the result set.</bullet>
<bullet>Scrollable cursors: calling %db2_fetch_row()% or %PDO->fetch()% with a specified row number enables you to skip to the right section and retrieve only the rows you want. Not supported by Apache Derby.</bullet>
<bullet>Standard SQL2003 approach: Subselect %ROW_NUMBER OVER(ORDER BY ~column~) AS number, ~column~% -- but this is currently only supported by DB2 and Oracle, so we cannot rely on that for Apache Derby.</bullet>
</list>
<example><![CDATA[SELECT * FROM (
SELECT ROW_NUMBER OVER (ORDER BY column) AS rownum, id, cost
FROM items
WHERE cost < 595
)
WHERE rownum >= 10 AND rownum <= 20
]]></example>
</slide>

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<slide>
<title>Assessing the depth of penetration</title>
<blurb>
First, let's find every PHP file where the application calls a mysql function:
</blurb>
<example>find . -name '*.php' -exec grep -Hil 'mysql_' {} \;
-H with filename
-i ignore case (because PHP is case-insensitive, MYSQL_ is possible but quite rare)
-l list only the filename, with no context for the match
</example>
<blurb>
Results in:
</blurb>
<example>./FREDts/content/geshi/geshi/php-brief.php
./FREDts/content/geshi/geshi/php.php
./components/com_user/user.php
./installation/install2.php
./installation/install4.php
./installation/index.php
./modules/mod_stats.php
./administrator/components/com_checkin/admin.checkin.php
./administrator/components/com_admin/admin.admin.html.php
./includes/feedcreator.class.php
./includes/phpInputFilter/class.inputfilter.php
./includes/database.php
./includes/getids.php
</example>
<blurb>
Hmm. FRED doesn't offer a clean separation of database functionality,
either by database (everything is calling the %mysql_% functions directly inline)
or by type of functionality (for example, there isn't a %*_db.php% file corresponding
to each subdirectory).
</blurb>
<blurb>
One way of proceeding would be to abstract the database functionality into a
separate layer so that the correct database implementation can be loaded based
on a configuration option.
</blurb>
</slide>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<slide>
<title>Abstracting database access</title>
<blurb>
The %updateOrder()% function contains examples of the custom class in action like:
</blurb>
<example type="php"><![CDATA[$this->_db->setQuery( "UPDATE $this->_tbl"
. "\nSET ordering='".$orders[$i]->ordering."' WHERE $k='".$orders[$i]->$k."'"
);
$this->_db->query();]]></example>
<blurb>
Apart from being rather hard to read, we can see that the %setQuery()% and %query()%
methods are nothing more than painfully crafted reimplementations of %prepare()%
and %execute()% methods that have existed in most standard database APIs for
decades, and which has been introduced to PHP is a standard API as
PHP Data Objects (PDO).
</blurb>
<blurb>Let's rewrite this using PDO:
</blurb>
<example type="php"><![CDATA[$query = $this->_db->prepare("UPDATE $this->_tbl
SET ordering = ?
WHERE $this->_tbl_key = ?";
$query->execute(array($orders[$i]->ordering, $orders[$i]->$k));]]></example>
<blurb>
While defining a basic database access abstraction layer for your application
seems like reinventing the wheel when things like PDO, MDB2, ADODB, and PEAR::DB
already exist, none of these were real options when FRED was being developed. Still,
a cautionary tale for you: make sure you abstract the right layer of your application.
</blurb>
</slide>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<slide>
<title>Resources</title>
<blurb>*Apache Derby*</blurb>
<link leader="Apache Derby project- " href="http://db.apache.org/derby" />
<link leader="Apache Derby: Off to the Races- " href="http://www.amazon.com/exec/obidos/tg/detail/-/0131855255" />
<link leader="ibm_db2 extension for PHP- " href="http://php.net/ibm_db2" />
<blurb>*SQL Portability*</blurb>
<link leader="A Gentle Introduction to SQL (includes SQL92 BNF) - " href="http://sqlzoo.net" />
<link leader="Comparison of different SQL implementations - " href="http://troels.arvin.dk/db/rdbms/" />
<link leader="SQL in a Nutshell- " href="http://www.oreilly.com/catalog/sqlnut2" />
<link leader="SQL API Portability: Mysql-isms To Be Avoided for API Portability- " href="http://home.fnal.gov/~dbox/SQL_API_Portability.html" />
<blurb>*Database abstraction layers*</blurb>
<link leader="ADOdb- " href="http://adodb.sourceforge.net" />
<link leader="MDB2- " href="http://pear.php.net/mdb2" />
<link leader="PEAR::DB- " href="http://pear.php.net/db" />
<blurb>*Standard PHP API for database access*</blurb>
<link leader="PHP Data Objects (PDO)- " href="http://php.net/pdo" />
</slide>

View File

@@ -0,0 +1,35 @@
<?xml version='1.0' encoding='iso-8859-1'?>
<slide>
<title>Database schemas: indexes</title>
<blurb>
Using standard keywords: KEY vs. INDEX
</blurb>
<example type="sql"><![CDATA[CREATE TABLE `#__categories` (
`id` int(11) NOT NULL auto_increment,
-- ...
PRIMARY KEY (`id`),
KEY `cat_idx` (`section`,`published`,`access`),
KEY `idx_section` (`section`),
KEY `idx_access` (`access`),
KEY `idx_checkout` (`checked_out`)
) TYPE=MyISAM;
]]>
</example>
<blurb>
KEY here is not standard SQL -- this is actually a MySQL shortcut for creating
indexes on columns within the table. The CREATE INDEX statement is a much more
portable syntax across databases (including MySQL).
</blurb>
<example type="sql"><![CDATA[CREATE TABLE #__categories (
id int(11) NOT NULL auto_increment,
-- ...
PRIMARY KEY (id);
CREATE INDEX cat_idx ON #__categories (section, published, access);
CREATE INDEX idx_section ON #__categories (section);
CREATE INDEX idx_access ON #__categories (access);
CREATE INDEX idx_checkout ON #__categories (checked_out);
]]>
</example>
</slide>

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<slide>
<title>Creating schemas portably</title>
<blurb>
Database abstraction layers also try to solve the problem of creating portable schemas.
</blurb>
<list>
<bullet>
Both MDB2_Schema and ADOdb XML Schema (AXMLS) define an XML format (different formats, of course), that their respective abstraction layer can use to create a schema for different databases.
</bullet>
<bullet>
Both handle basic data types, as well as more sophisticated features like sequences, auto-increment columns, UNIQUE and NOT NULL constraints, indexes, and primary keys.
</bullet>
</list>
<example type="sql" title="MDB2_Schema example"><![CDATA[<?xml version="1.0" encoding="ISO-8859-1" ?>
<database>
<name><variable>database</variable></name>
<create><variable>create</variable></create>
<overwrite><variable>overwrite</variable></overwrite>
<table>
<name>ce_bad_word</name>
<declaration>
<field>
<name>badword_id</name>
<type>integer</type>
<notnull>true</notnull>
<default>0</default>
</field>
<field>
<name>word</name>
<type>text</type>
<length>255</length>
<notnull>true</notnull>
<default> </default>
</field>]]></example>
<example type="sql" title="ADOdb-xmlschema example"><![CDATA[<?xml version="1.0" encoding="ISO-8859-1" ?>
<schema version="0.2">
<table name="users">
<desc>A typical users table for our application.</desc>
<field name="userId" type="I">
<descr>A unique ID assigned to each user.</descr>
<KEY/>
<AUTOINCREMENT/>
</field>
<field name="userName" type="C" size="16"><NOTNULL/></field>
<index name="userName">
<descr>Put a unique index on the user name</descr>
<col>userName</col>
<UNIQUE/>
</index>
</table>]]></example>
</slide>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<slide>
<title>Database schemas: date / time values</title>
<blurb>
Another fun one -- there are many columns defined as follows:
</blurb>
<example><![CDATA[`checked_out_time` datetime NOT NULL default '0000-00-00 00:00:00',
]]></example>
<blurb>
Two problems here:
</blurb>
<list>
<bullet type="number">DATETIME is not a part of standard SQL; SQL92 defines DATE,
TIME, and TIMESTAMP. TIMESTAMP in MySQL is a special column that automatically
inserts the current date and time when a row is inserted, which explains why
they use DATETIME instead. Most other databases use ~special registers~ to request
a special value, such as %CURRENT TIMESTAMP%.
</bullet>
<bullet type="number">Second problem is that TIMESTAMP's string signature
of YYYY-MM-DD hh:mm:ss should not accept all zeros -- what date and time is pure
zeros? And in the context of the application, this special value does not actually
help the data; a NULL value would be just as useful in identifying rows without a
timestamp.
</bullet>
</list>
</slide>

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<slide>
<title>Database schemas</title>
<blurb>
So far we have not found any actual schema for the database -- that is, the
structure of the database tables and columns. This is another area that
databases tend to differ in rather creative ways. FRED contains all of the
CREATE TABLE statements in %installation/sql/FRED.sql%. Let's take a look and
try to spot some potential problems:
</blurb>
<example type="sql"><![CDATA[CREATE TABLE `#__bannerclient` (
`cid` int(11) NOT NULL auto_increment,
`name` varchar(60) NOT NULL default '',
`extrainfo` text NOT NULL,
`checked_out` tinyint(1) NOT NULL default '0',
`checked_out_time` time default NULL,
PRIMARY KEY (`cid`)
) TYPE=MyISAM;]]></example>
<list>
<bullet effect="hide">
%TYPE=MyISAM%: this clause will make any database other than MySQL choke.
Note also that the table type gives you speed at the expense of transactions,
relational integrity, row locking, and other standard database features.
</bullet>
<bullet effect="hide">
Quotation marks: most databases do not allow you to put single-quotes around table
names, column names, and other SQL identifiers and will return an error message.
You can use double-quotes, but these indicate to the database that the SQL
identifier will be case-sensitive.
</bullet>
<bullet effect="hide">
The auto_increment clause used to define an identity column may not be
understood by most databases, as this is non-standard DDL. "Standard" DDL
defines an auto-incrementing column using the GENERATED ALWAYS AS IDENTITY
clause instead:
</bullet>
</list>
<list class="indent" effect="hide">
<bullet>DB2 and Apache Derby offer the GENERATED ... IDENTITY CLAUSE</bullet>
<bullet>PostgreSQL offers the SERIAL data type</bullet>
<bullet>Microsoft SQL Server offers the IDENTITY data type attribute</bullet>
<bullet>Oracle uses a combination of sequences and triggers to simulate an identity
column.</bullet>
</list>
<list>
<bullet effect="hide">
TEXT is a non-standard data type that best maps to a
VARCHAR(65,535) or CLOB(64K) standard data type.
</bullet>
<bullet effect="hide">TINYINT (an integer accepting values from 0-255 or -128 to 128)
can be mapped best to either a plain old INTEGER or a NUMERIC(3,0).
In other cases in the DDL, the TINYINT column is defined with a maximum of 1 or 3
-- this simply reduces the portability of the schema to other databases.
I would say that a more portable
approach would be to define a CHECK CONSTRAINT for a plain INTEGER column to
ensure that the value is less than a defined value -- however, MySQL in turn
does not support that. Of course, MySQL (until version 5, with STRICT MODE turned on)
would simply convert a value outside of the defined maximum to the maximum
value.
</bullet>
<bullet effect="hide">The "special" table name prefix %#__% is a way of avoiding table name clashes with other applications stored in the same database. Apache Derby, like most database servers, supports the use of ~schemas~ to provide a namespace for tables. For example, application %dboy%'s tables could be created in the %dboy% schema and accessible as %dboy.table1%, %dboy.table2% without conflicting with %dgirl.table1%, %dgirl.table2% existing in the same database.</bullet>
</list>
</slide>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<slide>
<title>The subject application</title>
<blurb>The application we'll be looking at (name changed to FRED to protect the innocent) is a well-known PHP content management system
that has been in existence for over five years. No slights are intended against the
developers of the code -- they created it with the intention of originally supporting
MySQL alone.</blurb>
<blurb>But that makes it a perfect candidate for our purposes.</blurb>
<list>
<bullet>It reflects a common "V1.0" application development scenario
resulting from the literal adoption of the LAMP stack.</bullet>
<bullet>FRED is now morphing into a "commercial support available for a fee" software
package that requires broader database support.</bullet>
<bullet>FRED is running into the same problems that we will see as we walk through the code.</bullet>
</list>
<blurb>~Note~: I have an idea of how FRED's team is approaching some
of these problems, and can share those with you along the way, but any
approach requires trade-offs of some kind.</blurb>
</slide>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<slide>
<title>The target database</title>
<blurb>The goal I will be working towards is porting this application so that it will run
against a standards-compliant database. Yes, *standards* is a loaded term.</blurb>
<blurb>We'll be looking at porting to Apache Derby, which has as its project charter the
goal of being the SQL92 standards-compliant database engine for the Apache DB project.</blurb>
<link leader="Apache Derby project - " href="http://db.apache.org/derby" />
<blurb>When you write for Apache Derby, you gain almost complete upwards compatibility with DB2
(a standards-compliant and standards-setting database).</blurb>
<blurb>~Full disclosure~: I wrote a book on Apache Derby coming out in the next couple of months,
and as an IBM employee DB2 pays my mortgage and puts food on my table.</blurb>
</slide>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<slide>
<title>Finally...</title>
<blurb class="big-center-blurb" align="center" margintop="3em">Thank you for being a |3333cc|wonderful| audience!</blurb>
<blurb class="big-center-blurb" align="center" margintop="3em">Dan Scott</blurb>
<link class="medium-center-blurb" align="center" text="dan@coffeecode.net" href="mailto:dan@coffeecode.net" />
<link class="medium-center-blurb" align="center" href="http://coffeecode.net" />
</slide>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<slide template="titlepage">
<title>Welcome!</title>
</slide>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<slide>
<title>Why not code purely for MySQL?</title>
<list>
<bullet>MySQL allows many interesting variations on standard SQL</bullet>
<bullet class="indent">In some cases, these can be considered developer-friendly</bullet>
<bullet class="indent">These can also considered to be hindrances to supporting multiple databases</bullet>
<bullet>Your hosting service might provide different databases (for example, hub.org offers PHP with PostgreSQL)</bullet>
<bullet>You may be interested in enhancing your portable, standard database development skills</bullet>
<bullet>Potential customers of your software might have standardized software environments and required a different database. Prediction from Mark Driver of Gartner Consulting on Sept. 13, 2005:</bullet>
</list>
<blurb class="nested-indent">*PHP will become a mainstream enterprise tool.*</blurb>
<link class="nested-indent" href="http://trends.newsforge.com/trends/05/09/14/0653243.shtml?tid=138&amp;tid=18" />
<blurb>~Note~: Recent releases of MySQL show that it, too, is adopting more
standard SQL -- so this presentation should help you port your MySQL application
to MySQL, as well.</blurb>
</slide>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<slide>
<title>Reflections</title>
<list>
<bullet>Portability is hard. Refactoring an application that was hardcoded for a single database is a daunting task.</bullet>
<bullet>Make your porting life easier by basing your application on standard SQL, then optimizing with database-specific tweaks where needed.</bullet>
<bullet>There's a lot to be said for using the Model-View-Controller pattern to design your application. If your model is implemented in text files or a database partitioned over dozens of servers, the view and controller elements of the application shouldn't even notice.</bullet>
<bullet>If each page in your application requires lots of nested dynamic includes as a result of your desire to support multiple databases, and it is affecting performance, consider designing the application installer as a simple precompiler that resolves all of the database-based includes at install time and places them statically in your pages.</bullet>
<bullet>If you are using PHP 5.x, consider using PDO as your primary native database access interface. You won't have to worry about significant API differences, and will be able to focus on plain old SQL and database differences instead.</bullet>
<bullet>If you are using PHP 4.x, consider using a database abstraction layer (MDB2, ADOdb, PEAR::DB) with an opcode cache to minimize API differences. Remember that it might take time for new database APIs to be added to any particular abstraction.</bullet>
<bullet>You might be surprised by what databases your users are going to want to run your application on in the future. Leave some room in your design to accept their generous contribution of a model that supports their database.</bullet>
</list>
</slide>

View File

@@ -0,0 +1,200 @@
<style title="Default" type="text/css">
body {
font-size: 10pt;
margin-top:0em;
margin-left:0em;
margin-right:0em;
margin-bottom:0em;
background-image: url(slides/intro/background.png);
background-attachment : fixed;
background-repeat : repeat
}
div.mainarea {
margin-left: 1em;
margin-right: 1em;
}
div.blurb {
margin-top: 0.5em;
font-size: 2em;
}
div.navbar_title {
font-size: 2em;
}
div.sticky {
margin: 0px;
position: fixed;
top: 0em;
left: 0em;
right: auto;
bottom: auto;
width: auto;
}
div.bsticky {
margin: 0px;
position: fixed;
top: auto;
left: 0em;
right: auto;
bottom: 0em;
width: 100%;
}
div.shadow {
background: #777777;
padding: 0.5em;
}
div.navbar {
background: url(images/trans.png) transparent fixed;
padding: 4px;
margin: 0px;
height: 6em;
color: #ffffff;
font-family: verdana, tahoma, arial, helvetica, sans-serif;
z-index: 99;
}
div.emcode {
background: #cccccc;
border: thin solid #000000;
padding: 0.5em;
font-family: monospace;
}
div.output {
font-family: monospace;
background: #eeee33;
border: thin solid #000000;
padding: 0.5em;
}
table.index {
background: #cccccc;
border: thin dotted #000000;
padding: 0.5em;
font-family: monospace;
font-size: 1.5em;
}
td.index {
background: #cccccc;
padding: 1em;
font-family: monospace;
font-size: 1.5em;
}
h1 {
font-size: 2em;
}
p,li {
font-size: 2em;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.c2right {
margin : 1em 1em 0em 0em;
padding-left : 1%;
padding-right : 1%;
border-style : solid;
border-top-width : 1px;
border-right-width : 1px;
border-bottom-width : 1px;
border-left-width : 1px;
border-right-color : inherit;
border-left-color : inherit;
width : 46%;
float : right;
}
.c2rightnb {
margin : 1em 1em 0em 0em;
padding-left : 1%;
padding-right : 1%;
width : 46%;
float : right;
}
.c2left {
margin : 1em 1em 0em 0em;
padding-left : 1%;
padding-right : 1%;
border-style : solid;
border-top-width : 1px;
border-right-width : 1px;
border-bottom-width : 1px;
border-left-width : 1px;
border-right-color : inherit;
border-left-color : inherit;
width : 46%;
float : left;
}
.c2leftnb {
margin : 1em 1em 0em 0em;
padding-left : 1%;
padding-right : 1%;
border-style : none;
width : 46%;
float : left;
}
.box {
margin : 0em 0em 0em 0em;
padding-left : 1%;
padding-right : 1%;
border-style : solid;
border-top-width : 1px;
border-right-width : 1px;
border-bottom-width : 1px;
border-left-width : 1px;
border-right-color : inherit;
border-left-color : inherit;
float : left;
}
A.linka { text-decoration: none; color: #000000; }
td.foo {color: #ffffff; font-family: arial,verdana,helvetica; font-size: 70%}
span.c4 {position: fixed; bottom: 0.5em; right: 4em; top: auto; left: auto; color: #ffffff; font-family: arial,verdana,helvetica; font-size: 70%}
td.c3 {color: #CC6600; font-family: arial, helvetica, verdana}
span.c2 {color: #ffffff; font-family: arial,hevetica,verdana}
span.c5 {position: fixed; bottom: 0.5em; right: 1em; top: auto; left: auto; color: #000000; font-family: arial,verdana,helvetica; font-size: 80%}
td.c1 {font-family: arial,helvetica,verdana; font-size: 80%}
tt { font-size: 0.8em; }
div.link {
font-size: 2em;
}
.indent {
margin-left: 2em;
font-size: 1.8em;
}
.nested-indent {
margin-left: 4em;
font-size: 1.8em;
}
.example-title {
font-weight: bold;
font-size: 2em;
}
ul.indent > div > li {
font-size: 1em;
}
div.example {
background-color: #cccccc;
font-size: 1.5em;
font-family: courier, monospace;
border-width: thick;
border-style: outset;
border-color: #000000;
}
div.medium-center-blurb {
font-size: 3em;
text-align: center;
padding-top: 1em;
}
div.big-center-blurb {
font-size: 5em;
text-align: center;
padding-top: 1em;
}
</style>