Products | Scripts | Services | Tutorials | Books | Links | Contact | Bulletin Board

The CITY Shop

Overview | Concept | Licensing | Demo/Download | Installation | Gallery | Last Updates | FAQ

Templates | Views | Engines | Libraries | Main Setup | Shop Setup | Database | Session | Shipping

Database | DBI/SQL

The database on this system is accessed through the DBLib.pm. For the standard shop, all the tables are in flat-file format. Support for DBI is implemented in the release due in a couple days, but even then, the interface in the shop will stay the same. The DBLib.pm will contain a wrapper for DBI which will translate the queries as needed. The object constructor will also make the connections with the database(s), as necessary, allowing the data tables to be spread across different systems.

The reason we included the file system in the standard shop, is that not everybody has a SQL database installed, together with DBI support, and for the most shops out there that have a limited amount of items and hits, the file-based system is so much easier to implement and maintain.

If you open a SQL connection, that will take about 0.2 seconds. That means, 0.2 seconds added to every click, if you don't use mod_perl. In the same 0.2 seconds, you can make at least 2 average file queries, if the files don't have more than, say, 100 records. This delay makes SQL look slow for small shops.

If you plan to use in your shop a database with more than 4000 items, SQL is a better fit for that, alone because one database search in flat file format might take longer than 3 seconds (ymmv). If you are willing to put up with that kind of delay, and your site does not experience high traffic, then you can still stay on a flat-file based system.

If your shop database has a couple hundred items or less, we recommend using the flat-file system over the SQL, since in this case the flat-file system is not only faster, but also easier to implement, maintain, and backup.

Database recommendation and tools

Although the shop implements a generic DBI support, we recommend the use of MySQL, due to its speed and ease of use. Typically a MySQL database software is installed in 10 minutes, the shop is being hooked up in another 2 minutes. Of course, if you want to customize the databases, you do have some work ahead, but it will still be feasible, with basic knowledge.

For the MySQL database management, we recommend DBTools, which you can find at:

http://dbtools.vila.bol.com.br/

 

The database of the generic shop

We will list below the tables used in this system's database. All of them are described in the ShopSetup.pm using a named hash. This is referenced in the $S->{-DB}, with the generic name of the table. As we mentioned before, they are all in flat-file format, but can be placed later in a SQL database, on a per/table basis.

Here is, for example, the hash describing the discount table:

my $DBDiscount = {
-TYPE => 'File',
-FILE_NAME => 'discount.db',
-FILE_PATH => $Path."Data_files",
-FIELD_NAME => [qw(ItemID DiscountID DiscountRange DiscountRate)],
-FIELD_KEY => 'ItemID',
-FIELD_TYPE => {'ItemID' => 'auto'},
};

Here is a list of the keys appearing in the hash above, and their description:

-TYPE - is the type of the table, where the "File" is the only value supported right now. When the DBI wrapper will be done, the reference to another type of database will be in the format similar to: DBI::MyDatabaseName, or even ODBC::MyDSNName. We will build a separate structure to describe the database access (host, userid, password).

-FILE_NAME - is typical to the file-based table, and contains only the file name, without path.

-FILE_PATH - is the path to the file described above. It allows us to reference files outside of the shop directory, if we feel the need to use common tables for multiple shops.

-FIELD_NAME - is the array of fields in this table

-FIELD_KEY - contains he name of the field that is the key of this table

-FIELD_TYPE - is an hash of field types, for those fields that are not generic. We chose to keep this as simple as possible, this is why we chose the use of a hash.
The only field that you might need to mention in here is the key field itself, if it is an autoincrement field. Its value needs to be set in that case on "auto".
In previous versions of the DBLib we used to support multiple fields, thus you might see some keys still described as "multiple" in the ShopSetup.pm. Since we had to make extensive changes to the DBLib.pm to support the DBI wrapper, we chose to temporarily remove the support for other field types than the "auto", which is needed for the key field.
We plan to re-implement them, after the DBI wrapper is completed, and also offer support for other field types, like one-way or two-way encrypted fields. Since the DBI does not offer support for this kind of fancy, we will build the translation routines for them in the wrapper.

Other valid keys to a table description hash are:

-CREATE_IF_MISSING - set this value if you want this table/file to be created, with the first write attempt. If not set, the script will exit with an error.

Tables found in the generic shop database

Here is a list of the tables used currently by the system, and described in the ShopSetup.pm, under the named hash %DatabaseList, and referenced in $S->{-DB}:

user

This table contains the user authentication data. The password is saved in clear-text right now. We will implement soon another methods. The user record has also a time stamp of the user's first login, and a flag to show if the user is enabled or not. This last field is not actively used right now.

userbilling, usershipping

These two tables save the billing and the shipping addresses of the users. Every user can have one or more records. The users have access to editing their data in these tables, through the "My Account" interface of the shop.

usercreditcard

As the both tables above, the credit card record contains all the credit card information for one payment mode. Every user can have more than one credit card on file. For the time being, this data is recorded unencrypted in this table.

order

The order table contains all the data referring an order on file. It holds the full billing and shipping addresses that were used for this order, the weight, price, discount, tax, shipping method and cost, as well as a flag of the processed status of this order.

The order contents are saved in the field "OrderItems" in a URL-like format. This field contains all the items ordered, by item id, the options associated with them, as well as the individual prices charged for the items and options. This was necessary, since the item prices and the discounts can change with the time, but the order should contain the data available at the ordering time.

Note that the shop saves the orders on an individual basis, in the Logs/orders directory of the shop, as a backup. The credit card or check information is being saved in those files, as they are actually the content of the mail sent to the shop admin, for every placed order.

product

This is the product table. It contains all the fields necessary to describe the product, including the language-specific contents. The options valid for every item are saved in the field "ProductOptions", in a double-colon ("::") separated string.

stock

The stock table contains the quantity of items available for every item that is supposed to have an active stock. Not all the items need to be in this table. If the shop uses the stock table to build an order, and some items were not found in here, the stock processing for those items will be ignored.
There is also a flag field for the backorder: set this field on "1" to allow this item to be backordered, leave it empty or set on "0" to disable backordering of the item.

categories

This table contains the data about the categories. Currently only two levels of nesting are supported in the display of categories, although the table concept allows an unlimited number of category levels.
You can implement your own naming system, we chose to keep the round numbers for the top-level categories, and reserve two digits for every nested level. Here is an example, taken from the generic shop database:

30000||PC Software|PC Software
30001|30000|Graphic|Graphik
30002|30000|Programming|Programmierung
30003|30000|Games|Spiele
30004|30000|Operating Systems|Betriebsysteme

As you see, the top level categories have an empty string for the parent category value.

upsell

This table is used by the upsell/cross-sell mechanism of the shop. The records here are the areas of the shop or the categories that use this feature, and contain the list of item ID's that are to be used for that.
You will see items like "main", "cart", "search", these are the shop areas, the rest are category ID's.
The upsell mechanism is as follows:

The value of the "UpsellMax" determines how many items will be displayed at one time. If the list of items contains more items than the value of "UpsellMax", a random list of items will be selected from the main list, avoiding the upsell of items that are already in the main page view. This process is explained in the code, please see the ShopProductBuilder.pm.

manufacturers

this is a very simple table, meant as an example of how you could build support for a manufacturer page. In a real-life scenario, you might want to add fields with images links, detailed description, and other data refering the manufacturer.

options

The option table contains the data for the options used by the products in the product table. The options ID's are not related in any way to a certain product. The record contains the language-specific content needed to build the option html, as well as the prices, the value selected by default, and the display type. You will define here if the option appears as a select, radio, or checkbox.

Here are as an example, two lines of the table (each line is broken in three, cause of the length):

100015|select||0::5%::10%|
  Warranty|Choose your Warranty|no warranty::3 Year Plan::5 Year Plan|
  Garantie|Garantie|keine::3 Jahres Plan::5 Jahres Plan
100016|select||0::-2::4|
  Color|Choose the Color|Blue::Green::Red|
  Farbe|Waehlen Sie die Farbe|Blau::Gruen::Rot

You can mix fix amounts and percents as you wish, negative values are allowed.

discount

The discount table describes the discounts applied to every item, depending on the quantity ordered. You can customize the code, if you wish, to apply discount for other fields, but the generic shop uses only the ordered quantity as criteria for the discount.

Every discount has an ID, that we will find in the product table, associated to the items. The "DiscountRange" field defines the min/max quantity that enables this discount. The "DiscountRate" contains the fix amount or the percent applied to the discount.

Here is an example:

103|1|1|1::5::10|0::10%::20%|English Description|German description

The first field is the discount ID, followed by the flag that enables the discount, then a field that flags the discount as "one-time" (global), when set on 1. That means, when set, the discounted amount will be applied only once, for any number of products falling in the range.

The following fields are the thresholds for the discount, the actual discount values applied to them, and finally the discount description (interesting for the manager interface).

Other tables

You may see other tables described in the ShopSetup.pm, like "session" and "cart"; these are processed internally by the SessionLib.pm and CartLib.pm and their structure will be explained in detail in the description of those packages.

 

DBI / SQL

The shop offers support for SQL databases, through the DBI interface. Since the shop's own DBLib.pm module started with a simplified implementation of database access, which has proven itself to be very easy to use and reasonably scalable, we decided to encapsulate the interface to DBI in the DBLib.pm itself, preserving the user interface.

There are a few steps involved in getting up and running with a SQL database. We will give here the example of a MySQL database, but we are confident that anything that will run under DBI, will work the same in The CITY Shop. The only databases that will not work are those that do not implement placeholders in the query building, at this time we are aware only of the DBD::ADO that does not (yet) implement them.

The methods translated in the plugin to DBI do not include fancy like table joins, bind output values, and such, also for the reason of portability. Using the model we provide, you can have a table on a MySQL, a couple more on an Oracle, or Microsoft SQL, and maybe later on you can get your user information from a LDAP database, when we will provide an interface module.

We will provide at a later date, a way for you to pass on a preformatted SQL query, but in the first stage, we chose to keep things simple.

Implementing SQL support

Since setting up a SQL database is not a trivial matter, we chose to offer the following install logistic:

  • start the shop with flat-files and get it up and running
  • install SQL, define your database user and password
  • transfer the tables from the flat files to SQL

You have currently only a one-way migration path to SQL, from the flat files, and this has its reasons. The main reason for it is that the SQL database allows for characters like "\n", "\r", "\t", as well as pure binary content, which is not supported in the case of the flat files. In our flat file model, we convert the newlines, carriage returns, and tabs to spaces, thus slightly affecting the content.

Although you can do a lot of fancy with a SQL database, keep in mind the purpose of the shop, and filter your data, whether your database implementation supports it or not, for the sake of portability.

Before you start

Ask your ISP to set you up a database on the local machine, and provide you with an user ID and password, with full access to that database. You will also ask to have the DBI interface installed in the Perl directory, along with the DBD module for your SQL database. This is also a step that your ISP should perform. If this is your own machine, you can install the DBI/DBD modules, using the CPAN interface.

Start the shop with flat files

Install the shop the regular way, testing all the functions, place orders, use the database manager, get familiar with the basic functionality of the shop. We recommend not to rename the "city" shop to anything else, since it is easier to get support using the standard version.

Define your database structure

Open the ShopDBSetup.pm, and look through the example of the shop tables. They all point to the database "city" and are defined in the global hash %TableConfig. If you choose to use a different database, named for example: "mydb", please update here the hash keys, like:

%TableConfig = (
  'mydb.product' => {
    -TABLE_DESCRIPTION => qq~
      ProductID VARCHAR(16) NOT NULL,
      ...

This library is used by the setupdb.cgi, to create the tables in the database(s), and transfer the data from the flat files to the SQL. Feel free to experiment and keep as a backup the flat files, until you are sure that you are up and running, and everything works.

Update your shop setup file

Open the ShopSetup.pm and scroll down to the end of the file.

First thing you have to do, is to update the database definition hash, as in the example:

$self->{-DATABASES} = {
  'mydb' => {
    'mydb' => {
      -DB_NAME   => 'mydb',
      -DB_TYPE   => 'dbi:mysql',
      -DB_PARAMS => {
        PrintError  => 0, # we handle the errors in the DBLib.pm
        RaiseError  => 0,
        LongReadLen => 128 * 16384,
      },
    -DB_HOST            => '',
    -DB_PORT            => '',
    -USER_ID            => 'myuserid',
    -USER_PASSWORD      => 'mypassword',
    -DEFAULT_TABLE_TYPE => 'MyISAM',
  },
};

Then, you will find next the $DBITableMap assignment, where all the shop table names are translated to SQL table names. You need to update here too, the correct database name, like in the example:

my $DBITableMap = $self->{-DBI_TABLE_MAP} = {
  'categories' => 'DBI.mydb.categories',
  'upsell'     => 'DBI.mydb.upsell',
  ... 

Then, scroll up a bit, in the main hash definition, right before the end, there is a key holding an array reference of tables to be migrated. In our example we migrated almost all the tables:

 -DB_TABLES_IN_TRANSITION => [qw(categories upsell usercreditcard          
  orderdata options discount states accountdata product order coupons
  accountuserdata manufacturers promotions manageruser userbilling 
  user processing countries paymentdata usershipping stock)],

If you plan keeping some tables in flat file format, just remove their names from the array. We have to point here, that these names are shop table names, and not the database table names.

Transfer the tables to SQL

Run the setupdb.cgi, with the parameter shop set to your shop name:

http://www.mydomain.com/cgi-bin/store/setupdb.cgi?&shop=city

This script will create the tables and transfer the data from the flat files, to the SQL database. Before creating the tables, the eventually existing tables will be deleted, therefore once you are done with it, remove the setupdb.cgi from your script root!

Discussion on method and performance issues

We choose this method to migrate to SQL because in most cases all you get from your ISP is a database user and a password, some times not even set up correctly. In some cases, you might have quite an experience, until you are all set up.

Asking the ISP to set your tables is not an option, since you might want to change that or play with it until you get where you want, so you need full control on your database.

If you run SQL, we recommend installing, or require to have installed mod_perl, since this is the only way your script will be fast. Here is an example, how this timing is calculated:

In the case of standard perl, a typical click results in querying about 4 files, which will take some 0.3 seconds, added to the 1 to 2 seconds to load the Perl interpreter. If you use DBI/SQL, you do not open files, but you open a database connection instead, which takes about the same amount of time. We are safe then to assume that the server side of things will put a load on your web server for 1.3 till 2.3 seconds, with every click.

If you install mod_perl, the script modules are loaded, parsed, and kept in memory for the whole time the web server is loaded. If you are uploading stuff and testing it, we recommend to use the non-mod_perl until your shop is stable, cause in order to make the shop aware of the changes, you need to restart the web server, most of the times your ISP will not smile at you for that. On the bright side of things, your time per click gets down considerably. For example, the flat-file model with 50 items (demo shop) will take 0.2 seconds, while the SQL model will take 0.062 seconds(62 miliseconds). If you have a flat file database with over 1000 items, your timing for flat files will skip over the 1 second mark, while the SQL model will still stay around 0.08 seconds (80 miliseconds).

It is safe to assume, that if you use mod_perl and SQL, you can provide at least 15 clicks per second, competing easily with other professional ecommerce solutions. Of course, this timing depends on the machine parameters, and could be worse if you share one machine with other processor-intensive applications. Our benchmarks were reported on a 800MHz PIII running Apache and MySQL on Win2000, but we saw better results on BSDi or Linux.