Synchronizing XML file to MySQL database - mysql

My company uses an internal management software for storing products. They want to transpose all the products in a MySql database so they can do available their products on the company website.
Notice: they will continue to use their own internal software. This software can exports all the products in various file format (including XML).
The syncronization not have to be in real time, they are satisfied to syncronize the MySql database once a day (late night).
Also, each product in their software has one or more images, then I have to do available also the images on the website.
Here is an example of an XML export:
<?xml version="1.0" encoding="UTF-8"?>
<export_management userid="78643">
<product id="1234">
<version>100</version>
<insert_date>2013-12-12 00:00:00</insert_date>
<warrenty>true</warrenty>
<price>139,00</price>
<model>
<code>324234345</code>
<model>Notredame</model>
<color>red</color>
<size>XL</size>
</model>
<internal>
<color>green</color>
<size>S</size>
</internal>
<options>
<s_option>aaa</s_option>
<s_option>bbb</s_option>
<s_option>ccc</s_option>
<s_option>ddd</s_option>
<s_option>eee</s_option>
<s_option>fff</s_option>
...
<extra_option>ggg</extra_option>
<extra_option>hhh</extra_option>
<extra_option>jjj</extra_option>
<extra_option>kkk</extra_option>
...
</options>
<images>
<image>
<small>1234_0.jpg</small>
</image>
<image>
<small>1234_1.jpg</small>
</image>
</images>
</product>
<product id="5321">
...
</product>
<product id="2621">
...
</product>
...
</export_management>
Some ideas for how can I do it?
Please let me know if my question is not clear. Thanks
EDIT:
I used a SQL like this for each table to fill them with the XML datas:
LOAD XML LOCAL INFILE '/products.xml' INTO TABLE table_name ROWS IDENTIFIED BY '<tag_name>';
Then, checking the tables content I can see that the field "id" (primary key) automatically has mantained itself the same for each respective product row in each tables. That's correct and suprisingly awesome!
The problem now is for the parameter <options> because it contains sub-parameters with same name (<s_option> and <extra_option>). The values of these tags are always different (that is, there is no a specific list of values, they are inserted manually by an employee) and also I don't know how many are for each product. I read that storing them as an array is not so good but if it's the only simple solution I can get it.

The way that I would approach the problem in your case is:
Create a respective set of corresponding tables in the database which in turn will represent the company's Product model by extracting the modelling from your given XML.
Create and use a scheduled daily synchronization job, that probably will executes few SQL commands in order to refresh the data or introduce a new one by parsing the products XMLs into the created tables.
To be more practical about it all:
As for the database's tables, I can easily identify three tables to be created based on your XML, look at the yellow marked elements:
Products
ProductsOptions
ProductsImages
(This diagram created based on an XSD that was generated from your XML)
All rest can be considered as regular columns in the Products table since they're constitutes a 1-1 relationship only.
Next, create the required tables in your database (you can use an XSD2DB Schema converter tool to create the DDL script, I did it manually):
companydb.products
CREATE TABLE companydb.products (
Id INT(11) NOT NULL,
Version INT(11) DEFAULT NULL,
InsertDate DATETIME DEFAULT NULL,
Warrenty TINYINT(1) DEFAULT NULL,
Price DECIMAL(19, 2) DEFAULT NULL,
ModelCode INT(11) DEFAULT NULL,
ModelColor VARCHAR(10) DEFAULT NULL,
Model VARCHAR(255) DEFAULT NULL,
ModelSize VARCHAR(10) DEFAULT NULL,
InternalColor VARCHAR(10) DEFAULT NULL,
InternalSize VARCHAR(10) DEFAULT NULL,
PRIMARY KEY (Id)
)
ENGINE = INNODB
CHARACTER SET utf8
COLLATE utf8_general_ci
COMMENT = 'Company''s Products';
companydb.productsimages
CREATE TABLE companydb.productimages (
Id INT(11) NOT NULL AUTO_INCREMENT,
ProductId INT(11) DEFAULT NULL,
Size VARCHAR(10) DEFAULT NULL,
FileName VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (Id),
CONSTRAINT FK_productsimages_products_Id FOREIGN KEY (ProductId)
REFERENCES companydb.products(Id) ON DELETE RESTRICT ON UPDATE RESTRICT
)
ENGINE = INNODB
AUTO_INCREMENT = 1
CHARACTER SET utf8
COLLATE utf8_general_ci
COMMENT = 'Products'' Images';
companydb.productsoptions
CREATE TABLE companydb.productoptions (
Id INT(11) NOT NULL AUTO_INCREMENT,
ProductId INT(11) DEFAULT NULL,
Type VARCHAR(255) DEFAULT NULL,
`Option` VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (Id),
CONSTRAINT FK_producstsoptions_products_Id FOREIGN KEY (ProductId)
REFERENCES companydb.products(Id) ON DELETE RESTRICT ON UPDATE RESTRICT
)
ENGINE = INNODB
AUTO_INCREMENT = 1
CHARACTER SET utf8
COLLATE utf8_general_ci;
As for the synchronisation job process to take place, you can easily create an MySql event and use the Event Scheduler to control it, I created the required event which is calling a stored-procedure that you'll find below (SyncProductsDataFromXML), look:
CREATE DEFINER = 'root'#'localhost' EVENT
companydb.ProductsDataSyncEvent ON SCHEDULE EVERY '1' DAY STARTS
'2014-06-13 01:27:38' COMMENT 'Synchronize Products table with
Products XMLs' DO BEGIN SET #productsXml =
LOAD_FILE('C:/MySqlXmlSync/products.xml'); CALL
SyncProductsDataFromXML(#productsXml); END;
ALTER EVENT companydb.ProductsDataSyncEvent ENABLE
Now the interesting part is taking place, here is the synchronization stored-procedure (note how the event above is calling it):
CREATE DEFINER = 'root'#'localhost'
PROCEDURE companydb.SyncProductsDataFromXML(IN productsXml MEDIUMTEXT)
BEGIN
DECLARE totalProducts INT;
DECLARE productIndex INT;
SET totalProducts = ExtractValue(productsXml, 'count(//export_management/product)');
SET productIndex = 1;
WHILE productIndex <= totalProducts DO
SET #productId = CAST(ExtractValue(productsXml, 'export_management/product[$productIndex]/#id') AS UNSIGNED);
INSERT INTO products(`Id`, `Version`, InsertDate, Warrenty, Price, ModelCode, Model, ModelColor, ModelSize, InternalColor, InternalSize)
VALUES(
#productId,
ExtractValue(productsXml, 'export_management/product[$productIndex]/version'),
ExtractValue(productsXml, 'export_management/product[$productIndex]/insert_date'),
CASE WHEN (ExtractValue(productsXml, 'export_management/product[$productIndex]/warrenty')) <> 'false' THEN 1 ELSE 0 END,
CAST(ExtractValue(productsXml, 'export_management/product[$productIndex]/price') as DECIMAL),
ExtractValue(productsXml, 'export_management/product[$productIndex]/model/code'),
ExtractValue(productsXml, 'export_management/product[$productIndex]/model/model'),
ExtractValue(productsXml, 'export_management/product[$productIndex]/model/color'),
ExtractValue(productsXml, 'export_management/product[$productIndex]/model/size'),
ExtractValue(productsXml, 'export_management/product[$productIndex]/internal/color'),
ExtractValue(productsXml, 'export_management/product[$productIndex]/internal/size')
);
SET #totalImages = ExtractValue(productsXml, 'count(//export_management/product[$productIndex]/images/image)');
SET #imageIndex = 1;
WHILE (#imageIndex <= #totalImages) DO
INSERT INTO productimages(ProductId, Size, FileName) VALUES(#productId, 'small', EXTRACTVALUE(productsXml, 'export_management/product[$productIndex]/images/image[$#imageIndex]/small'));
SET #imageIndex = #imageIndex + 1;
END WHILE;
SET #totalStandardOptions = ExtractValue(productsXml, 'count(//export_management/product[$productIndex]/options/s_option)');
SET #standardOptionIndex = 1;
WHILE (#standardOptionIndex <= #totalStandardOptions) DO
INSERT INTO productoptions(ProductId, `Type`, `Option`) VALUES(#productId, 'Standard Option', EXTRACTVALUE(productsXml, 'export_management/product[$productIndex]/options/s_option[$#standardOptionIndex]'));
SET #standardOptionIndex = #standardOptionIndex + 1;
END WHILE;
SET #totalExtraOptions = ExtractValue(productsXml, 'count(//export_management/product[$productIndex]/options/extra_option)');
SET #extraOptionIndex = 1;
WHILE (#extraOptionIndex <= #totalExtraOptions) DO
INSERT INTO productoptions(ProductId, `Type`, `Option`) VALUES(#productId, 'Extra Option', EXTRACTVALUE(productsXml, 'export_management/product[$productIndex]/options/extra_option[$#extraOptionIndex]'));
SET #extraOptionIndex = #extraOptionIndex + 1;
END WHILE;
SET productIndex = productIndex + 1;
END WHILE;
END
And you're done, this is the final expected results from this process:
NOTE: I've commit the entire code to one of my GitHub's repositories: XmlSyncToMySql
UPDATE:
Because your XML data might be larger then the maximum allowed for a TEXT field, I've changed the productsXml parameter to a MEDIUMTEXT. Look at this answer which outlines the various text datatypes max allowed size:
Maximum length for MYSQL type text

As this smells like integration work, I would suggest a multi-pass, multi-step procedure with an interim format that is not only easy to import into mysql but which also helps you to wrap your mind around the problems this integration ships with and test a solution in small steps.
This procedure works well if you can flatten the tree structure that can or could be expressed within the XML export into a list of products with fixed named attributes.
query all product elements with an xpath query from the XML, iterate the result of products
query all product attributes relative to the context node of the product from the previous query. Use one xpath per each attribute again.
store the result of all attributes per each product as one row into a CSV file.
store the filenames in the CSV as well (the basenames), but the files into a folder of it's own
create the DDL of the mysql table in form of an .sql file
run that .sql file against mysql commandline.
import the CSV file into that table via mysql commandline.
You should get quick results within hours. If it turns out that products can not be mapped on a single row because of attributes having multiple values (what you call an array in your question), consider to turn these into JSON strings if you can not prevent to drop them at all (just hope you don't need to display complex data in the beginning). Doing so would be violating to target a normal form, however as you describe the Mysql table is only intermediate here as well, I would aim for simpleness of the data-structure in the database as otherwise queries for a simple and fast display on the website will create the next burden.
So my suggestion here basically is: Turn the tree structure into a (more) flat list for both simplification of transition and easier templating for display.
Having an intermediate format here also allows you to replay in case things are going wrong.
It also allows you to mock the whole templating more easily.
Alterantively is is also possible to store the XML of each project inside the database (keep the chunks in a second table so you can keep varchar (variable length) fileds out of the first table) and keep some other columns as (flat) reference columns to query against. If it's for templating needs, turning the XML into a SimpleXMLElement is often very nice to have it being a structured, non-primitive data-type as view object you can traverse and loop over options. Would work similar with JSON however keeping the XML would not break a format boundary and XML can also express more structure than JSON.

You're taking a very technology-centered approach to this. I think it's wise to start by looking at the functional specifications.
It helps to have a simple UML class diagram of the business class Product. Show its attributes as the business sees them. So:
How is Model related to Product? Can there be multiple Models for one Product or the other way around?
What kind of data is stored in the Internal element? In particular: How can Internal's color and size be different from Model's color and size?
And specifically about the web application:
Is the Web application the only application that will be interested in this export?
Should the web application care about versioning or simply display the last available version?
Which specific options are interesting to the web application. Like a discount property or vendor name property or others?
What should the Product details page look like, what data needs to be displayed where?
Will there be other pages on the website displaying product information, and what product information will they list?
Then you'll know which attributes need to be readily available to the web application (as columns in the Product table) and (perhaps) which ones may be simply stored in one big XML blob in the database.

Related

IS this redundent data?

I think I know the answer but I am not confident enough yet to just go with it.
I am thinking i have redundant info that can be fixed by just making a table for the the description . Below in my monster inserts i have 'mdesc' it has the same data that is in the dialogue inserts i just have it as 'intro'. Should i make a monster description table to hold a mdescid and and description? something like this
CREATE TABLE `mdesc`(
`mdescid` int(15) UNSIGNED NOT NULL,
`monsterdesc` varchar(50) NOT NULL
);
SO then i could just put the mdescid in both my dialogue table and the monsters table and get rid of the intro insert
--monster inserts
INSERT INTO monster(monsterid,monstername,monsterloc,mdesc,weaponval)values(1,dragon,11,'a dragon',1);
INSERT INTO monster(monsterid,monstername,monsterloc,mdesc,weaponval)values(2,spider,8,'a poisonus spider',2);
INSERT INTO monster(monsterid,monstername,monsterloc,mdesc,weaponval)values(3,wasps,7,'a swarm of wasps',3);
INSERT INTO monster(monsterid,monstername,monsterloc,mdesc,weaponval)values(4,enchantress,13,'a seductive enchantress',4);
INSERT INTO monster(monsterid,monstername,monsterloc,mdesc,weaponval)values(5,bellyfish,5,'a slimy belly fish',5);
-- dialogue inserts
INSERT INTO dialogue(monsterid,intro,attack,outcome1,outcome2,outcome3)
values(1,'"a dragon."','"you fight and,"','" kill it with your sword!"','"it kills and eats you."','" you both run away!"');
There's no reason to spread the info for one monster over three tables. That's called a one-to-one relationship, one row of a table relates to only row of another table, and vice versa. It's rarely useful.
What you could do is change dialogue to have the monster, situation, and text.
create table monster_dialogues (
monster_id integer not null
foreign key(monster_id) references monsters(id)
situation varchar(255) not null,
dialogue varchar(255) not null
unique(monster_id, situation)
)
This is a one-to-many relationship, each monster has many dialogues. This avoids having to add a new column every time you have a new situation.
And instead of reproducing the same basic text over and over, have a default in your code.
-- Note, avoid including formatting like quotes and conjunctions in your data.
-- It restricts how the data can be used.
-- Leave the formatting of dialogue into sentences to the application.
select coalesce(dialogue, 'you fight')
from monster_dialogues
where monster_id = ?
and situation = 'attack'
Note that using a database for this is probably overkill. Unless there's a very large number of monsters or you need to search through a large number of dialogues a simple JSON file would do.
{
"dragon": {
"damage": 100,
"health": 200,
"dialogues": {
"attack": "you fight",
}
}
}

mysql filter using like from another table

I know how to do this with cursors, but am wondering if it's possible without.
I'm analyzing traffic to my website, keeping track of IP address, requested URL, agent, referrer, hostname, etc. I'm using a stored procedure to perform the insert and logic.
From traffic I've already logged, I've identified additional actions I want to take based on the details of the visit:
1 - If the request is for /wp-* , /test, /admin, /backup, /old, /wordpress* (and more), this is an exploit probe (this isn't a wordpress site). I want the IP address blacklisted automatically.
2 - If the request is a social media hit (visible via the URL requested and/or the referrer), our marketing person wants the system to send her a text message immediately. It inserts info into the po_box table, which sets off a trigger to send.
3 - If the agent is python-requests* or one of multiple bots ignoring my robots.txt, I want the IP address blacklisted automatically.
I'm looking for something along the lines of "if requested_url like (select exploit_prefix from known_exploits)" or "if requested_url like (select sm_query from social_med_links) or http_referrer like (select sm_referral from social_media_links)" or "if http_referrer like (select bot_ref from bad_bots) or user_agent like (select bot_agent from bad_bots)"
I'm doing it with cursors right now, but am looking for a more elegant way. Even if it's slower than cursors (would be surprising), I'm curious if it can be done.
#Sloan Thrasher: I intentionally didn't go into details about how the back-end works. It works as it is, and discussing it derails from the actual question (such as the #danblack comment that the exploits I identified aren't exploit probes. I have determined that they are, based on evidence.) I'm asking if it's possible to move away from cursors, and use (hopefully more efficient) queries.
One of the first functions is to log the page visit, which determines if the request is violating the site, or the IP address has already been blacklisted. If the IP is in the blacklist table (either previously or because of the current request), the server passes a 403. I double check every new address added to the blacklist, and move to .htaccess if appropriate, or change the rules if not appropriate.
create table bad_bots (
bot_ref varchar(16) comment 'use like %xxx% compare',
bot_agent varchar(16) comment 'use like %xxx% compare',
blacklist_comment varchar(32) comment 'push to blacklist table'
);
Some of the bot_ref entries:
http://pizza-imperia.com/
Some of the bot_agent entries:
%MJ12bot%
%NetcraftSurveyAgent%
%Uptimebot%
%zgrab%
%python-requests%
create table blacklist (
blacklist_ip varchar(32) primary key,
blacklist_added datetime not null default now(),
blacklist_comment varchar(32)
);
create table known_exploits(
exploit_prefix varchar(64) primary key comment 'use like xxx% compare,
blacklist_comment varchar(32) comment 'push to blacklist table'
);
A partial list of the prefixes. #danblack - each is an attempt to access aspects of the site which are common exploit avenues. NO ONE has any right to access my /admin directory without express authorization, and legitimate content scans know that. This list has been developed by looking up the IPs who have made these requests, and they exist in multiple blacklists for detailed exploit probes.
/admin%
/adminer.php%
/xmlrpc.php%
/demo%
/backup%
/test%
/main%
/new%
/old%
/wp/wp-login.php%
/wordpress/wp-login.php%
(Identifies hits off of social media accounts or google/yahoo/bing/chamber of commerce, etc.)
create table social_media_links (
sm_referral varchar(32) comment 'use like %xxx% compare,
sm_query varchar(32) comment 'use like %xxx% compare',
sm_owner varchar(16) comment 'use for po_box'
);
A partial list of the sm_referral entries:
%.facebook.com/%
%.instagram.com/%
%.yelp.com/%
%thurstonchamber.com%
Only one sm_query so far:
%?fbclid=%
create table page_visits (
visit_ip_address varchar(32),
visit_time datetime not null default now(),
visit_url varchar(128),
visit_agent varchar(64),
visit_referrer varchar(128)
);
create procedure sp_log_page_visit (
IN var_visit_ip_address varchar(32),
IN var_visit_url varchar(128),
IN var_visit_agent varchar(64),
IN var_visit_referrer varchar(128)
)
begin
declare is_blacklisted int default 0;
insert into page_visits (visit_ip_address, visit_url, visit_agent, visit_referrer)
values (var_visit_ip_address, var_visit_url, var_visit_agent, var_visit_referrer);
# I'M CURRENTLY USING A CURSOR FOR THIS...LOOKING FOR SIMPLER QUERY SIMILAR TO...
if(var_visit_agent like (select bot_agent from bad_bots where bot_agent is not null)
or var_visit_referrer like (select bot_ref from bad_bots where bot_ref is not null)) then
insert ignore into blacklist (blacklist_ip) values (var_visit_ip_address);
set is_blacklisted = 1;
end if;
# I'M CURRENTLY USING A CURSOR FOR THIS...LOOKING FOR SIMPLER QUERY SIMILAR TO...
if(var_visit_url like (select exploit_prefix from known_exploits)) then
insert ignore into blacklist (blacklist_ip) values (var_visit_ip_address);
set is_blacklisted = 1;
end if;
# I'M CURRENTLY USING A CURSOR FOR THIS...LOOKING FOR SIMPLER QUERY SIMILAR TO...
if(var_visit_referrer like (select sm_referral from social_media_links)
or (var_visit_url like (select sm_query from social_media_links)) then
# performs multiple actions to add new entry into po_box table
end if
select is_blacklisted;
end ;;
Presuming you have a table known_exploits, a simple query will determine whether or not to blacklist a particular IP. Here's an example for your first query:
IF ((SELECT count(`exploit_prefix`) as `numfound`
FROM `known_exploits`
where `exploit_prefix` like var_visit_url) > 0)
THEN
insert ignore into `blacklist` (`blacklist_ip`) values (var_visit_ip_address);
set is_blacklisted = 1;
ENDIF;

Many to many relationship with a reverse lookup

MySQL version 5.5.35-log
I have an exceptionally large set of data which consists of a many-to-many relationship which is closely related to people shopping at outlets. A person may shop at many hundreds of different outlets, and similarly, many thousands of people may shop at any particular outlet. The overall number of people and outlets extends into the millions each.
I have a situation where checking if a person shops at a particular outlet must be resolved quickly, so I opted to use reverse lookups; i.e. each 'person' row stores a list of ID's for the stores they shop in. Due to the volume of data, a third relationship table is presumed to be unsuitable; i.e. one which has a row for each persons outlets. My assumption here is that it would have little choice but to produce table scans through many, many rows.
To store this reverse lookup in MySQL however, SET is also unsuitable as it has a maximum of 64 entries which is of course not enough in this situation. So, I opted for a BLOB which is structured as simply a block containing each 4 byte little-endian ID.
But, a different problem arises here; When it comes time to find if an outlet ID is contained in the BLOB using SQL, unusual things start occuring. From other questions, it seems the only way to do this is using SUBSTRING with the BLOB in a loop, however this doesn't seem to work; SUBSTRING is returning a blank string. First, here's some code:
CREATE FUNCTION `DoesShopAt`(shopperID INT UNSIGNED,outletID TINYBLOB) RETURNS VARCHAR(20)
BEGIN
-- Setup a loop. We're going to loop through each ID in the blob:
declare i_max int unsigned default 0;
declare i int unsigned default 0;
declare offset int unsigned default 0;
declare storeID tinyblob;
-- Setup the blob store - all the stops a particular shopper goes to:
declare allShops blob;
-- Grab the set of ID's - a blob of each 4 byte outlet ID:
select AllStores from Shoppers where ID=shopperID into allShops;
-- How many shops?
select length(allShops)/4 into i_max;
while i < i_max do
-- Grab the shops ID:
set storeID=substring(allShops,offset,4);
if outletID = storeID then
return "Yep, they do!";
end if;
-- Update the ID offset in the blob:
set offset=offset+4;
-- Update the loop counter:
set i=i+1;
end while;
return "Nope, they don't.";
END
For debugging purposes it is set to return a string. The intention is it returns true or false depending on if the given shopper does shop at the given outlet.
Ideally, this function would receive two numbers; the shopperID and the outletID, however converting the outletID into a block of 4 little endian bytes seems unreliable and slow at best as it must go via hex (as far as I can tell). So instead, the calling service provides the block of 4 bytes.
Interestingly though, returning storeID immediately after it is set results in a blank string. This is the case if the type of storeID is a varchar, binary or tinyblob; it seems no matter what, it is returning a blank string.
So as a final resort for testing purposes, I instead tried this:
set storeID=substring(hex(allShops),offset,8);
Ensuring that the offset counter was increased by 8 this time, and the input ID was adjusted to suit. Yet again though, it still was returning a blank string (again with return storeID immediately after it's set), even though the allShops data is non-zero.
Edit: Although I found the issue, I can't help but think that maybe there is a better approach to reverse lookups like this in MySQL; do you have any suggestions?
I started playing around with substring and realised what the issue was; offset is being initialised to 0 when it should be 1. Changing this then started correctly returning results:
declare offset int unsigned default 0;
Should have been:
declare offset int unsigned default 1;
However, please see the note at the bottom of the original question.

CakePHP can't find table after creating a table

I create a table directly by a query. I only want to Import some data. Therefor i execute a query which is built dynamicly and i try execute this query in a Component-class. (I use a random existing model to execute this query is there a better why?)
$query= "CREATE TABLE IF NOT EXISTS testerdbs (
'Ü1' varchar(6) COLLATE utf8_swedish_ci DEFAULT NULL,
'Ü2' varchar(6) COLLATE utf8_swedish_ci DEFAULT NULL,
'Ü3' int(3) DEFAULT NULL,
'Ü4' varchar(6) COLLATE utf8_swedish_ci DEFAULT NULL,
'Ü5' date DEFAULT NULL
)"
$data = ClassRegistry::init('import_files');
$data->query($query);
This works fine.
In the same request i want to access the created table in the controller.
App::import('Model', "testerdb");
//$this->loadModel("testerdb");
$newTable = ClassRegistry::init("testerdb");
echo '<pre>', print_r($newTable->getColumnTypes()), '</pre>';
If I try to execute this in same request i always get the error:
Error: Table testerdbs for model testerdb was not found in datasource default.
If I do exactly the same request again, everything works fine...
I google about an hour and it seemed that cake cache the model. If I execute this request again cake cache again all the tables and than cake find my new table. So I hoped to load or import the created Table in the same request, but i don't work.
Is there another way to load the table? Where is my mistake?
Thanks for help!
This might be a bit stale, but I just spent the last week trying to work around the problem and maybe this will help someone.
The root problem is that the cache of table names is initialized before you created the temporary table, so the 'setSource' function returns an error that the temporary table does not exist.
The solution is to overrid the 'setSource' function for the Model that you are creating for 'testerdb' and remove the check on table existence (i.e. everything within the test:
if (method_exists($db, 'listSources'))' )
Your model definition should look something like this:
App::uses('AppModel', 'Model');
class testerdb extends AppModel {
public function setSource($tableName) {
$this->setDataSource($this->useDbConfig);
$db = ConnectionManager::getDataSource($this->useDbConfig);
$this->table = $this->useTable = $tableName;
$this->tableToModel[$this->table] = $this->alias;
$this->schema();
}
}
Many thanks to whomever posted the link below. This has worked with my CakePHP 2.0 instance.
http://web2.0goodies.com/blog/uncategorized/mysql-temporary-tables-and-cakephp-1-3/
Why would you only want to have a temporary table? I would just temporarily store whatever data you are importing in an in-memory model or data-structure.
If the table is not temporary, then just create it statically before you run your program.

Codeigniter upgradable module logic for database process

i am trying to build my own cms with using Codeigniter.
I wrote some modules already. But, in time i made some changes with them.
Now, if i want to upgrade a module. I send the files with ftp and change database fields with phpmyadmin.
It takes a lot time and high possibility to mis something to change, and for every project i've use this module, i have to repeat these changes again.
Now, I am planning to make an installation system.
my Modules directory structure like below:
/modules
/modules/survey/
/modules/survey/config
/modules/survey/config/autoload.php
/modules/survey/config/config.php
/modules/survey/config/routes.php
/modules/survey/config/install.php
/modules/survey/controllers
/modules/survey/controllers/entry.php...
/modules/survey/models
/modules/survey/models/survey.php...
/modules/survey/views
/modules/survey/views/index.php...
I thought that all modules should have an install.php file in config directory. That is keeping the setting of releated module. Like below:
$config['version'] = 1.1; //or 1.2, 1.3 etc.
$config['module'] = 'Survey Module';
$config['module_slug'] = 'survey';
$config['module_db_table'] = 'module_survey';
I have an installed_modules table already:
id, module, module_slug, version
Now, i am trying to make an installation script. Like below:
Before start , I zip module's files.
1- upload zip file with an installation page to a temp directory
2- unzip the module in this temp direcorty
3- Find install.php
4- Get modules information from install.php
5- Check if this module already in installed_modules table.
6a) If it's not: I will make a new module_survey table. And copy this temp directory into the real modules directory.
6b) If it's already : I have to change the structure of this table without lossing the data added before. Delete all module files and copy the new ones from temp into the modules directory.
7- When everything done, Delete temp directory.
I stucked in 6a and 6b.
For 6a, How should i create e.x 'module_survey' table.
Should i add a $config['db_query'] in install.php like
$config['db_query'] = "CREATE TABLE IF NOT EXISTS `module_survey` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`lang_id` int(11) NOT NULL DEFAULT '1',
`usort` int(3) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
";
and run this query. Or what is your advice here? There maybe not just one table, there should 2 or more with relations each other for different modules.
and For 6b:
I thought, i should create a new temp table like named "temp_module_survey".
old fields =
$oldFields = $this->db->field_data('module_survey');
for new fields =
$newFields = $this->db->field_data('temp_module_survey');
compare fields which are newly added, which are deleted and which's fieldData has changed.
And
add new fields to oldTable
Delete unnecessary fields from oldTable
and update fields which's fieldData has changed.
Then remove temporary Table.
For a summary, What should i do for database changes without lossing the old data.
I hope i could explain.
Thank you.
Phil Sturgeon's codeigniter-migrations can help.