|
29.Oct.2015: Note
Thanks for your reading of my old contents. Feasibility study of "Prestashop for PostgreSQL"In order to support PostgreSQL to PrestaShop, I made a feasibility study for a few weeks. Write the conclusion first, I was not able to complete the installation, because there were many difficulties which due to the MySQL's expanded functions. However, I think that supporting PostgreSQL is certainly feasible. 0.1. Source and install
1. Findings and Extracted problemsBut not all, I think I got some findings and was able to extract a lot of problems. 1.1. Grave accent (`) problem (non-standard: MySQL expand)
The first difficulty was the huge number of Grave accent.
[< MySQL >] SELECT `language_code` FROM `'._DB_PREFIX_.'lang` WHERE `iso_code` = \''.pSQL(strtolower($iso_code)).'\'' I had to rewrite this as follow: [< PostgreSQL >] SELECT language_code FROM '._DB_PREFIX_.'lang WHERE iso_code = \''.pSQL(strtolower($iso_code)).'\'' It's simple. However, it was very hard work of rewriting the sheer number of them. Note:Since this is non-standard notation, I think all should be deleted. 1.2. Table DefinitionsI was able to rewrite all CREATE TABLE queries in "install/data/db_structure.sql". 1.2.1. Data Type
PostgreSQL does not support UNSIGNED attribute. Therefore, I was emulated using check constraint. [< MySQL >] CREATE TABLE unsigned_table ( col int unsigned ); [< PostgreSQL >] CREATE TABLE unsigned_table ( col int check(0 <= col) ); All auto_increment integer type have been rewritten to Serial type. [< MySQL >] CREATE TABLE test_table ( col int auto_increment, data int ); [< PostgreSQL >] CREATE TABLE test_table ( col serial, data int ); /* for last_insert_id() function */ ALTER SEQUENCE IF EXISTS test_table_col_seq RENAME TO test_table_auto_increment;
PostgreSQL does not support ENUM type.
I made some new types using CREATE TYPE queries.
[< MySQL >] CREATE TABLE `new_type_test` ( `id` ENUM('red', 'green', 'blue') NOT NULL DEFAULT 'red' ); [< PostgreSQL >] CREATE TYPE color_type AS ENUM ('red', 'green', 'blue'); CREATE TABLE new_type_test ( id color_type NOT NULL DEFAULT 'red' ); I made nine types below: CREATE TYPE group_type AS ENUM ('select', 'radio', 'color'); CREATE TYPE type AS ENUM('products', 'categories', 'attributes', 'manufacturers', 'suppliers'); CREATE TYPE status AS enum('open','closed','pending1','pending2'); CREATE TYPE redirect_type AS ENUM('', '404', '301', '302'); CREATE TYPE condition AS ENUM('new', 'used', 'refurbished'); CREATE TYPE visibility AS ENUM('both', 'catalog', 'search', 'none'); CREATE TYPE reduction_type AS ENUM('amount', 'percentage'); CREATE TYPE method AS enum('GET','POST','PUT','DELETE','HEAD'); CREATE TYPE management_type AS ENUM('WA', 'FIFO', 'LIFO'); 1.2.2. CREATE TABLEIn the table definitions, indices have to defined separately. I show two examples: [< MySQL >] CREATE TABLE `PREFIX_accessory` ( `id_product_1` int(10) unsigned NOT NULL, `id_product_2` int(10) unsigned NOT NULL, KEY `accessory_product` (`id_product_1`,`id_product_2`) ) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8; [< PostgreSQL >] CREATE TABLE PREFIX_accessory ( id_product_1 int NOT NULL check(0 <= id_product_1), id_product_2 int NOT NULL check(0 <= id_product_2) ); CREATE INDEX PREFIX_accessory_accessory_product1 ON PREFIX_accessory (id_product_1,id_product_2); [< MySQL >] CREATE TABLE `PREFIX_attribute_impact` ( `id_attribute_impact` int(10) unsigned NOT NULL auto_increment, `id_product` int(11) unsigned NOT NULL, `id_attribute` int(11) unsigned NOT NULL, `weight` DECIMAL(20,6) NOT NULL, `price` decimal(17,2) NOT NULL, PRIMARY KEY (`id_attribute_impact`), UNIQUE KEY `id_product` (`id_product`,`id_attribute`) ) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8; [< PostgreSQL >] CREATE TABLE PREFIX_attribute_impact ( id_attribute_impact serial NOT NULL , id_product int NOT NULL check(0 <= id_product), id_attribute int NOT NULL check(0 <= id_attribute), weight DECIMAL(20,6) NOT NULL, price decimal(17,2) NOT NULL, PRIMARY KEY (id_attribute_impact), CONSTRAINT id_product UNIQUE (id_product,id_attribute) ); 1.3. INSERTI was worried beforehand, but there was no big problem of INSERT statements. 1.3.1. INSERT ON DUPLICATE KEY UPDATE (non-standard: MySQL expand)
INSERT ON DUPLICATE KEY UPDATE queries are used in about 10 places.
This statement is non-standard, but those were able to rewrite easily.
[< Original: classes/Product.php:3146 >] public static function addFeatureProductImport($id_product, $id_feature, $id_feature_value) { return Db::getInstance()->execute(' INSERT INTO `'._DB_PREFIX_.'feature_product` (`id_feature`, `id_product`, `id_feature_value`) VALUES ('.(int)$id_feature.', '.(int)$id_product.', '.(int)$id_feature_value.') ON DUPLICATE KEY UPDATE `id_feature_value` = '.(int)$id_feature_value ); } [< After change >] public static function addFeatureProductImport($id_product, $id_feature, $id_feature_value) { $count = Db::getInstance()->execute('SELECT count(*) FROM '._DB_PREFIX_.'feature_product WHERE id_feature = ' . (int)$id_feature . ' AND id_product = '. (int)$id_product); if ($count == 0) { return Db::getInstance()->execute('INSERT INTO '._DB_PREFIX_.'feature_product (id_feature, id_product, id_feature_value) VALUES ('.(int)$id_feature.', '.(int)$id_product.', '.(int)$id_feature_value.')'); } else { return Db::getInstance()->execute('UPDATE '._DB_PREFIX_.'feature_product SET id_feature_value = ' . (int)$id_feature_value . ' WHERE id_deature = ' . (int)$id_feature . ' AND id_product = ' . (int)$id_product); } } 1.3.2. INSERT IGNORE (non-standard: MySQL expand)INSERT IGNORE statement is also non-standard, and it can be rewritten in the same way as the REPLACE statement in principle. 1.3.3. "INSERT INTO table () VALUES()" query problem (non-standard: MySQL expand)1.4. REPLACE (non-standard: MySQL expand)REPLACE statement is useful, but is non-standard. Fortunately, it has not been used much.
There are two explicit REPLACE statements in the source code. Rewriting was somewhat complicated. [< Original classes/ProductSale.php:33 >] public static function fillProductSales() { $sql = 'REPLACE INTO '._DB_PREFIX_.'product_sale (`id_product`, `quantity`, `sale_nbr`, `date_upd`) SELECT od.product_id, COUNT(od.product_id), SUM(od.product_quantity), NOW() FROM '._DB_PREFIX_.'order_detail od GROUP BY od.product_id'; return Db::getInstance()->execute($sql); } [< After change >] public static function fillProductSales() { $ret = true; $sql = 'SELECT od.product_id as id_product, COUNT(od.product_id) as quantity, SUM(od.product_quantity) as sale_nbr, NOW() as date_upd FROM '._DB_PREFIX_.'order_detail od GROUP BY od.product_id'; $result = Db::getInstance()->execute($sql); foreach($result as $row) { $sql = 'SELECT count(*) FROM ' ._DB_PREFIX_.'product_sale WHERE id_product = ' .$row['id_product']; $count = Db::getInstance()->execute($sql); if ($count == 0) { $sql = 'INSERT INTO '._DB_PREFIX_.'product_sale (id_product, quantity, sale_nbr, date_upd) VALUES ('; $sql .= '\''.$row['id_product'].'\', \''.$row['quantity'].'\', \''.$row['sale_nbr'].'\', \''.$row['date_upd'].'\')'; $ret &= Db::getInstance()->execute($sql); } else { $sql = 'UPDATE '._DB_PREFIX_.'product_sale SET id_product = \'' .$row['quantity']. '\', sale_nbr = \'' .$row['sale_nbr']. '\', date_upd = \\'' .$row['date_upd'] . '\ WHERE id_product = \'' .$row['id_product']; $ret &= Db::getInstance()->execute($sql); } } return $ret; [< Original classes/Tab.php:125 >] /* Query definition */ $query = 'REPLACE INTO `'._DB_PREFIX_.'access` (`id_profile`, `id_tab`, `view`, `add`, `edit`, `delete`) VALUES '; $query .= '(1, '.(int)$id_tab.', 1, 1, 1, 1),'; foreach ($profiles as $profile) { $rights = $profile['id_profile'] == $context->employee->id_profile ? 1 : 0; $query .= '('.(int)$profile['id_profile'].', '.(int)$id_tab.', '.(int)$rights.', ' .(int)$rights.', '.(int)$rights.', '.(int)$rights.'),'; } $query = trim($query, ', '); return Db::getInstance()->execute($query); [< After change >] $ret = true; $sql = 'SELECT count(*) FROM ' ._DB_PREFIX_. 'access WHERE id_profile = 1 AND id_tab = ' . (int)$id_tab; $count = Db::getInstance()->execute($query); if ($count == 0) { $sql = 'INSERT INTO '._DB_PREFIX_.'access (id_profile, id_tab, view, add, edit, delete) VALUES '; $sql .= '(1, '.(int)$id_tab.', 1, 1, 1, 1),'; $ret &= Db::getInstance()->execute($sql); } else { $sql = 'UPDATE '._DB_PREFIX_.'access SET view = 1, add = 1, edit = 1, delete = 1 WHERE id_profile = 1 AND id_tab = '; $sql.= (int)$id_tab; $ret &= Db::getInstance()->execute($sql); } foreach ($profiles as $profile) { $rights = $profile['id_profile'] == $context->employee->id_profile ? 1 : 0; $query .= '('.(int)$profile['id_profile'].', '.(int)$id_tab.', '.(int)$rights.', '; $query .= (int)$rights.', '.(int)$rights.', '.(int)$rights.'),'; $sql = 'SELECT count(*) FROM ' ._DB_PREFIX_. 'access WHERE id_profile = ' .(int)$profile['id_profile']. ' AND id_tab = ' . (int)$id_tab; $count = Db::getInstance()->execute($query); if ($count == 0) { $sql = 'INSERT INTO '._DB_PREFIX_.'access (id_profile, id_tab, view, add, edit, delete) VALUES '; $sql .= '('.(int)$profle['id_profile'].','.(int)$id_tab.','; $sql .= (int)$rights.','.(int)$rights.','.(int)$rights.','.(int)$rights.'),'; $ret &= Db::getInstance()->execute($sql); } else { $sql = 'UPDATE '._DB_PREFIX_.'access SET view = '.(int)$rights.', add = '.(int)$rights.', edit = '; $sql .= (int)$rights.', delete = '.(int)$rights; $sql .= ' WHERE id_profile = ' .(int)$profile['id_profile'] . ' AND id_tab = ' . (int)$id_tab; $ret &= Db::getInstance()->execute($sql); } } return $ret;
REPLACE statement is used when data of access.xml is populated in the database.
1.5. UPDATE LIMIT statement (non-standard: MySQL expand)
When I was working, I saw some very strange queries.
One kind of them is "UPDATE LIMIT" statement.
This is of course a non-standard statement.
30 Query UPDATE `ps_configuration` SET `value` = '1',`date_upd` = '2013-03-17 22:50:15' WHERE `name` = 'MOD_BLOCKTOPMENU_SEARCH' AND id_shop_group IS NULL AND id_shop IS NULL LIMIT 1 30 Query UPDATE `ps_configuration` SET `value` = '2',`date_upd` = '2013-03-17 22:50:15' WHERE `name` = 'PRODUCTS_VIEWED_NBR' AND id_shop_group IS NULL AND id_shop IS NULL LIMIT 1 30 Query UPDATE `ps_configuration` SET `value` = 'graphartichow',`date_upd` = '2013-03-17 22:50:15' WHERE `name` = 'PS_STATS_RENDER' AND id_shop_group IS NULL AND id_shop IS NULL LIMIT 1 30 Query UPDATE `ps_configuration` SET `value` = 'graphgooglechart',`date_upd` = '2013-03-17 22:50:15' WHERE `name` = 'PS_STATS_RENDER' AND id_shop_group IS NULL AND id_shop IS NULL LIMIT 1 ... to be continued ..
I have no idea how to rewrite this.
1.6. SELECT
Almost SELECT queries did not need to be rewritten.
1.6.1. Last_Insert_Id function (non-standard: MySQL expand)1.6.2. SQL_CALC_FOUND_ROWS (non-standard: MySQL expand)nine places. 1.6.3. control flow function: IF(), IFNULL()Control flow functions IF() and IFNULL() are used in some places in the source code. These can be rewritten in CASE statement which is supported by MySQL and PostgreSQL. [< Original: classes/Configuration.php:113 >] $sql = 'SELECT c.`name`, cl.`id_lang`, IF(cl.`id_lang` IS NULL, c.`value`, cl.`value`) AS value, c.id_shop_group, c.id_shop FROM `'._DB_PREFIX_.'configuration` c LEFT JOIN `'._DB_PREFIX_.'configuration_lang` cl ON (c.id_configuration = cl.id_configuration)'; [< After change >] $sql = 'SELECT c.name, cl.id_lang, CASE WHEN cl.id_lang = NULL THEN c.value ELSE cl.value END AS value, c.id_shop_group, c.id_shop FROM `'._DB_PREFIX_.'configuration` c LEFT JOIN `'._DB_PREFIX_.'configuration_lang` cl ON (c.id_configuration = cl.id_configuration)'; 1.7. SHOW commandsSHOW TABLES command and SHOW COLUMNS FROM command can be rewritten using PL/pgSQL functions. 1.7.1. SHOW TABLESSHOW TABLE command is used in about ten places in the source code. I defined two functions called "show_tables", and rewrote SHOW TABLE command using those functions.
CREATE OR REPLACE FUNCTION show_tables (text) RETURNS SETOF name AS $$ /* $1 = pattern */ SELECT c.relname as "Name" FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r','s','') AND n.nspname !~ '^pg_toast' AND pg_catalog.pg_table_is_visible(c.oid) AND c.relname LIKE $1 ; $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION show_tables () RETURNS SETOF name AS $$ SELECT c.relname as "Name" FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN ('r','s','') AND n.nspname !~ '^pg_toast' AND pg_catalog.pg_table_is_visible(c.oid); $$ LANGUAGE SQL; I show two examples: [< Original classes/Language.php:472 >] $result = Db::getInstance()->executeS('SHOW TABLES FROM `'._DB_NAME_.'`'); [< After change >] $result = Db::getInstance()->executeS('SELECT SHOW_TABLES()'); [< Original classes/Language.php:391 >] $tables = Db::getInstance()->executeS('SHOW TABLES LIKE \''.str_replace('_', '\\_', _DB_PREFIX_).'%\_lang\' '); [< After change >] $tables = Db::getInstance()->executeS('SELECT SHOW_TABLES(\''.str_replace('_', '\\_', _DB_PREFIX_).'%\_lang\') ');
1.7.2. SHOW COLUMNS FROMSHOW COLUMNS (also SHOW FIELDS) command is used in about ten places in the source code. I defined a function called "show_columns_from", and rewrote SHOW COLUMNS command using this function.
CREATE OR REPLACE FUNCTION show_columns_from (name) RETURNS SETOF record AS $$ /* $1 = relname */ SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod) FROM pg_catalog.pg_attribute a WHERE a.attrelid = (SELECT c.oid FROM pg_catalog.pg_class c WHERE c.relname = $1 AND pg_catalog.pg_table_is_visible(c.oid)) AND a.attnum > 0 AND NOT a.attisdropped; $$ LANGUAGE SQL; I show one example: [< Original classes/Language.php:412 >] $columns = Db::getInstance()->executeS('SHOW COLUMNS FROM `'.$name.'`'); [< After change >] $columns = Db::getInstance()->executeS('SELECT * FROM SHOW_COLUMNS_FROM(\'' .$name. '\') AS (Field name, Type text)'); 1.8. Functions2. Conclusions
I think that more than 95% of queries which executed while PrestaShop is running are SELECT statements.
And then, almost PrestaShop's SELECT statements can be executed in PostgreSQL.
Indeed, it is not an easy task. However, I think that it is able to rewrite by a few engineers. Suzuki hironobu: hironobu _at_ interdb.jp
Last-modified: 2013-3-25
|