I've module and I've update to alter database table, shortly I need to do something like
ALTER TABLE `TABLE` ADD `FIELD` INT UNSIGNED NOT NULL AFTER `SOME_FIELD`
so is there any built in function in Drupal to make this changes I considered db_add_field function didn't work?
Sultan
Using db_add_field()
db_add_field('TABLE', 'FIELD', "VARCHAR( 255 ) NOT NULL DEFAULT '0' AFTER FIELD_2");
The above does not work, for one it leaves out the first argument (the reference to $ret) and the fourth argument will not allow a raw sql query, only a structured array.
What I had to do was this (change hook_update_N to modulename_update_XXXX as per the drupal api documentation of course):
function hook_update_N(&$sandbox) {
// We use update_sql here, instead of db_add_field because we cannot specify
// AFTER in the db_add_field.
$ret = array();
$ret[] = update_sql("ALTER TABLE {table} ADD `FIELD` INT UNSIGNED NOT NULL AFTER `SOME_FIELD`");
return $ret;
}
Hope this helps someone else.
Related
I want to use ENUM in my class but TINYINT in my database. I followed this article: https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/cookbook/mysql-enums.html
In my Mysql table:
CREATE TABLE `side` (
`coated` tinyint(1) DEFAULT NULL COMMENT '0 = Uncoated; 1 = Coated',
);
In my class:
/**
* #ORM\Column(type="string", columnDefinition="ENUM('coated', 'uncoated')")
*/
private $coated = null;
Running the values in PHP I get the real value from database:
0 or 1
I'm wondering if this solution works using Mysql. Hope having a solution, if this won't work the only solution I find is:
public function getCoated() {
if ($this->coated === 0){
return "uncoated";
} elseif ($this->coated === 1) {
return "coated";
} else {
return null;
}
}
Instead of using "TINYINT" I suggest to use "BIT" data type, so that database will only allow storing the values as "0" or "1" to be in safe side.
Your existing:
CREATE TABLE `side` (
`coated` tinyint(1) DEFAULT NULL COMMENT '0 = Uncoated; 1 = Coated'
);
Change it to:
CREATE TABLE `side` (
`coated` BIT DEFAULT NULL COMMENT '0 = Uncoated; 1 = Coated'
);
In "TINYINT" it can also accept the values other than 0/1 that might be create a bug in your application if someone updated your table data to like 2,3,4.
I want to develop website with option to select language
at the time I do not know about how to structure my database tables i.e
either I should add separate fields for each language e.g
tbl_posts
id, title_en,title_fr,description_en,description_fr,....
or should I get help of google translate at run time
or there is something else easy to do this
secondly I will need to have URLs like
www.domain.com/en/posts/ & www.domain.com/fr/posts/
third what other things should I keep in mind to develop multilingual website.
looking for standardized, more optimized, easy manageable and fully dynamic solution.
Cakephp
Step 1: In your lib/Cake/basic.php add function
if (!function_exists('__dbt')) {
function __dbt($text, $args = null) {
if($text==null){
return null;
}else{
$languageUse = Configure::read('Config.language');
if($languageUse!='en-us' && $languageUse!='eng'){
$modelName = ucfirst($languageUse)."Translation";
$model = ClassRegistry::init($modelName);
$data = $model->find('first',array('fields'=>array('translation'),'conditions'=>array("text"=>"$text")));
if(!empty($data[$modelName]) && $data[$modelName]['translation']!=''){
return $data[$modelName]['translation'];//die('1');
}else{
// Please copy & paste below code from your basic.php __() function
App::uses('I18n', 'I18n');
$translated = I18n::translate($text);
$arguments = func_get_args();
return I18n::insertArgs($translated, array_slice($arguments, 1));
}
}else{
// Please copy & paste below code from your basic.php __() function
App::uses('I18n', 'I18n');
$translated = I18n::translate($text);
$arguments = func_get_args();
return I18n::insertArgs($translated, array_slice($arguments, 1));
}
}
}
}
Step 2: create table on basis of language you want to use
Note: table name should be prefix locale + _tranlations
example: hin_translations, tha_translations etc.
CREATE TABLE IF NOT EXISTS `hin_translations` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`text` text NOT NULL,
`translation` text NOT NULL,
`created` datetime NOT NULL,
`modified` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
In above table add english string in your text column and its translation in translation column.
Step 3: where ever you want to change language string either from database or locale po file just use
__dbt("Write your string here");
:) enjoy your multilingual site
In CodeIgniter, I want to prep a value returned from a form so that if it is a 0, it will actually be inserted as NULL.
I created a function outside of my controller class:
function prep_zero_to_null($int)
{
if ($int == 0)
{
return NULL;
}
else
{
return $int;
}
}
And at the form validation, I do:
$this->form_validation->set_rules('category_id', 'Category',
'required|integer|prep_zero_to_null');
However, CI still tries to insert zeroes as '0' in the database, which breaks one of my foreign key constraints.
Interestingly enough, if I replace NULL by, say, 25 in the prep_zero_to_null function, CI will indeed recognize it and insert 25 instead of '0'. So my prepping function is indeed getting called, but CI won't allow NULL as a result of it and instead converts it to '0'.
How can I achieve what I want?
Edit: For those wondering, the category_id field does allow null:
`category_id` int(10) unsigned DEFAULT NULL
And the exact error is:
INSERT INTO `articles` (`category_id`, `order`, `title`, `text`)
VALUES ('0', '0', 'test', 'test')
^
Should be NULL
Cannot add or update a child row: a foreign key constraint fails
(`db`.`articles`, CONSTRAINT `articles_ibfk_1` FOREIGN KEY
(`category_id`) REFERENCES `categories` (`id`)
ON DELETE SET NULL ON UPDATE CASCADE)
Just looking at this quickly I think the problem is your $int == 0. Is $int an actual 0 type integer or is it a string? In which case the proper check would be $int == '0'.
Codeigniter validation functions doesn't set the field value based on what you return, your validation function should either return TRUE or FALSE to state that something is valid or not.
If you're after changing the value of something, you'll need to accept variable by reference and modify it in the function then you can return TRUE so it passes the validation.
The best solution would be to make the check before inserting the data into database & not relying on the validation library to do this kind of dirty work.
If you want to insert null to the database you need to return a string with the value "null".
function prep_zero_to_null($int) {
return ($int == 0) ? 'NULL' : $int;
}
Have you tried just unsetting the variable. It's a bit ugly, but it should return NULL for the value then.
Tested here.
I submitted an issue at the GitHub bug tracker for CodeIgniter, as this appears to be a bug.
https://github.com/EllisLab/CodeIgniter/issues/2563
Right now, the workaround at model level is the following:
$category_id = prep_zero_to_null($this->input->post('category_id'));
$data = array
(
'category_id' => $category_id,
'order' => $this->input->post('order'),
'title' => $this->input->post('title'),
'text' => $this->input->post('text')
);
Edit: Apparently, this is the right approach, as there should only be strings at validation/controller level.
I am working on a Yii project. How can I use the ON DUPLICATE feature of MySQL ( http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html ) when doing a save() on a Yii model?
My MySQL is as follows:
CREATE TABLE `ck_space_calendar_cache` (
`space_id` int(11) NOT NULL,
`day` date NOT NULL,
`available` tinyint(1) unsigned NOT NULL DEFAULT '0',
`price` decimal(12,2) DEFAULT NULL,
`offer` varchar(45) DEFAULT NULL,
`presale_date` date DEFAULT NULL,
`presale_price` decimal(12,2) DEFAULT NULL,
`value_x` int(11) DEFAULT NULL,
`value_y` int(11) DEFAULT NULL,
PRIMARY KEY (`space_id`,`day`),
KEY `space` (`space_id`),
CONSTRAINT `space` FOREIGN KEY (`space_id`) REFERENCES `ck_space` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
My PHP is a follows:
$cache = new SpaceCalendarCache();
$cache->attributes = $day; //Some array with attributes
$cache->save();
If there is a duplicate in my primary key (sapce_id,day), I don't want it to complain, I just want it to update with the latest data.
I know how to do it in raw SQL, I was just wondering if there is a clean Yii way to do it.
You are using models in Yii, its quite simple .. try to load you model where you suspect to have duplicate entries, if you find the entry the model is loaded else null is return. now if your model is null simply create new model. rest is your normal code to insert a new record.
//try to load model with available id i.e. unique key
$model = someModel::model()->findByPk($id);
//now check if the model is null
if(!$model) $model = new someModel();
//Apply you new changes
$model->attributes = $attributes;
//save
$model->save();
Refer to post controllers update method in sample app Yii blog. I might be wrong with spelling of function names, sorry for that.
I'm repeating two main points from previous answers I think you should keep:
Don't (try to) use "on duplicate key update" since its MySQL-only, as txyoji points out.
Prefer the select->if not found then insert->else insert demonstrated by Uday Sawant.
There's another point here, though: Concurrency. Although for low traffic applications the probability that you'll get in trouble is minimal (still never zero), I think we always be careful about this.
From a transactional point of view, "INSERT .. ON DUPLICATE UPDATE" is not equivalent to selecting into your application's memory and then inserting or updating. The first is a single transaction, then second is not.
Here's a bad scenario:
You do select your record using findByPk() which returns null
Some other transaction (from some other user request) inserts a record with the id you just failed to select
At the next instant you try to insert it again
In this case you'll either get an exception (if you're working with a unique key, as you do here) or a duplicate entry. Duplicate entries are much harder to pick up (usually nothing seems weird until your users see duplicate records).
The solution here is to set a strict isolation level, for example "serializable", and then begin a transaction.
Here's an example for yii:
Yii::app()->db->createCommand('SET TRANSACTION ISOLATION LEVEL SERIALIZABLE');
$trn = Yii::app()->db->beginTransaction();
try {
// Try to load model with available id i.e. unique key
// Since we're in serializable isolation level, even if
// the record does not exist the RDBMS will lock this key
// so nobody can insert it until you commit.
// The same shold for the (most usual) case of findByAttributes()
$model = someModel::model()->findByAttributes(array(
'sapce_id' => $sapceId,
'day' => $day
));
//now check if the model is null
if (!$model) {
$model = new someModel();
}
//Apply you new changes
$model->attributes = $attributes;
//save
$model->save();
// Commit changes
$trn->commit();
} catch (Exception $e) {
// Rollback transaction
$trn->rollback();
echo $e->getMessage();
}
You can see more about isolation levels at least in the following links and see what every isolation level has to offer in data integrity in exchange for concurrency
http://technet.microsoft.com/en-us/library/ms173763.aspx
http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html
I overrode beforeValidate() where I checked if a duplicate exists. If one does, I set $this->setIsNewRecord(false);
Seems to work. Not sure how performant it is.
The "On Duplicate Key Update" feature is specific to MySQL's dialect of SQL. Its unlikely to be implemented in any data abstraction layer. ZendDB and Propel don't have an equivalent.
You can simulate the behavior by attempting an insert in a try/catch and update if insert fails with the proper error code. (duplicate key error).
I agree with #txyoji's analysis of the problem, but I would use a different solution.
You can extend the save() method of the model to look for an existing record, and update it, or insert a new row if it doesn't.
you have to use try catch like that:
try{
$model->save();
}
catch(CDbException $e){
$model->isNewRecord = false;
$model->save();
}
Ooops, sorry.. this answer for yii2
If you dont use yii model, this function generates mysql syntax insert on duplicates key update
static function insertDuplicate($table, $columns, $duplicates, $values="",$ignores=false){
$params=array();
$names=array();
$tipe="VALUES";
$ignore=($ignores===true)?"IGNORE":"";
$placeholders=array();
if(is_array($columns)){
if(!isset($columns[0])){
foreach($columns as $name=>$value)
{
$names[]=$name;
if($value instanceof CDbExpression)
{
$placeholders[] = $value->expression;
foreach($value->params as $n => $v)
$params[$n] = $v;
}
else
{
$placeholders[] = ':' . $name;
$params[':' . $name] = $value;
}
}
}else{
$names=$columns;
}
$myColumn=implode(', ',$names);
if($values!=""){
$myValue=$values;
}else{
$myValue='('.implode(', ', $placeholders).')';
}
}else{
$myColumn=$columns;
$myValue=$values;
}
if($values!=""){
if(substr(strtoupper($values),0,6)=="SELECT"){
$tipe="";
}
}
$d = array();
if(is_array($duplicates)){
if(!isset($duplicates[0])){
foreach($duplicates as $duplicate=>$act)
{
if($act=="increase"){
$dup=$table.".".$duplicate . ' = '.$table.".".$duplicate.' + VALUES('.$duplicate.')';
}elseif($act=="decrease"){
$dup=$table.".".$duplicate . ' = '.$table.".".$duplicate.' - VALUES('.$duplicate.')';
}else{
$dup=$table.".".$duplicate . ' = VALUES('.$duplicate.')';
}
$d[] = $dup;
}
}else{
foreach($duplicates as $duplicate){
$dup=$duplicate . ' = VALUES('.$duplicate.')';
$d[] = $dup;
}
}
$myDuplicate= implode(', ', $d);
}else{
$myDuplicate=$duplicates;
}
$sql='INSERT '.$ignore.' INTO ' . $table
. ' (' . $myColumn . ') '.$tipe.' '
. $myValue . ' ON DUPLICATE KEY UPDATE ' .$myDuplicate;
return Yii::$app->db->createCommand($sql)->bindValues($params)->execute();
}
Place that function into someclass, and dont forget use
use yii\db\Command;
in that class
That function can insert on key update, update increment, update decrement, update multi from a value, and update from select
Usage :
//to update available=1 and price into 100
someclass::insertDuplicate(
'ck_space_calendar_cache',
['sapce_id'=>1,'day'=>'2022-09-01','available'=>1,'price'=>100],
['available','price']
);
//to update price increase by 100, (if price is decrease then change it to decrease)
someclass::insertDuplicate(
'ck_space_calendar_cache',
['sapce_id'=>1,'day'=>'2022-09-01','price'=>100],
['price'=>'increase']
);
//to update mass with a value
someclass::insertDuplicate(
'ck_space_calendar_cache',
['sapce_id','day','price'],
['price'],
'(1,'2022-09-01',100),(2,'2022-09-01',300),(3,'2022-09-01',100)'
);
//to update mass with select from another table
someclass::insertDuplicate(
'ck_space_calendar_cache',
['sapce_id','day','price'],
['price'],
'SELECT otherid as sapce_id, otherday as day, otherprice as price from other WHERE otherprice>100'
);
I'm adding "promo code" functionality to an online shopping cart written by someone else. This is a pretty micky mouse architecture question, but I would like to hear a couple of opinions. All three of my ideas would work, but which do you think is best for maintainability?
So, here's the basic table structure for the promo codes:
CREATE TABLE `promocodes` (
`promocode_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`promocode` VARCHAR( 255 ) NOT NULL ,
`description` VARCHAR( 255 ) NULL ,
`discount_percentage` INT NULL ,
`discount_dollars` DECIMAL( 6, 2 ) NULL ,
`expiration_date` DATE NULL ,
`uses_remaining` INT NULL ,
`is_active` BOOL NOT NULL DEFAULT '1'
)
What's the smartest (without getting overcomplicated):
Check existence with SQL, everything else seperately in code
// LIBRARY
function promoCodeExists($promoCodeString){
// make sql call
return $promoCodeDetailsHash; // or false if no record
}
function isPromoCodeUsable($promoCodeDetailsHash){
// check expiry date and number of uses left and active / inactive
}
function usePromoCode($promoCodeId){
// do order association
// decrement uses left
}
// USAGE
$promoCodeDetailsHash = promoCodeExists($promoCode);
if (is_array($promoCodeDetailsHash) AND isPromoCodeUsable($promoCodeDetailsHash)){
usePromoCode($promoCodeDetailsHash['id'])
} else {
// invalid promo code
}
Or, have a validation function but have it called only by the get function:
// LIBRARY
function validatePromoCode($promoCodeDetailsHash){
// check expiry date and number of uses left and active / inactive
}
function isPromoCodeUsable($promoCodeString){
// make sql call
return validatePromoCode($promoCodeDetailsHash); // or false if no record
}
// USAGE
$promoCodeDetailsHash = promoCodeExists($promoCode);
if (is_array(isPromoCodeUsable($promoCodeDetailsHash))){
usePromoCode($promoCodeDetailsHash['id'])
} else {
// invalid promo code
}
Check everything in SQL with invalid the same as nonexistance:
// LIBRARY
function getDetailsForUsablePromoCode($promoCode){
// use SQL WHERE clauses to only return existence for currently valid promo codes
// or false if no result
}
// USAGE
$promoCodeDetailsHash = getDetailsForUsablePromoCode($promoCode)
if (is_array($promoCodeDetailsHash)){
usePromoCode($promoCodeDetailsHash['id'])
} else {
// error state
}
Feel free to point out any other approaches or wackness here.
In my application, I create it as 2 table. First table just like you, but only hold the usage_limit as integer. In second table, I will hold the usage, one usage per row.
In the promocode_usage table, I will have promocode_id as foreign key, and other required column such as usage datetime, user id, etc. To check if the promo is still available, I will simply count the row in promocode_usage table that have promocode_id I want to check. If the result less than usage_limit value, the promo can be used.