среда, июня 17, 2009

Полезные заметки по symfony - 1

Установка meta html данных:

class SomeObjectActions extends sfActions
{
public function executeShowObject(sfWebRequest $request){
...
$this->getResponse()->setTitle($this->object->getMetaTitle());
$this->getResponse()->addMeta('meta_keywords', $this->object->getMetaKeywords());
$this->getResponse()->addMeta('meta_description', $this->object->getMetaDescription());
}
}

Правильное использование sfWidgetFormTextareaTinyMCE

sfWidgetFormTextareaTinyMCE - это виджет, HTML WYSIWYG editor. Очень удобная вешь, но в блоге symfony очень скудно описан принцип работы, точнее в последней версии возникает ошибка:

You must pass a "class" attribute for a TinyMCE widget

Вот, как и какой класс надо указывать:

$this->setWidget('text', new sfWidgetFormTextareaTinyMCE(array(
'width' => 650,
'height' => 550,
'config' => 'theme_advanced_disable: "anchor,image,cleanup,help"',
)
,array(
'class' => 'tinyMCE',
)
));


в генераторе (если вы используете его в backend) generator.yml укажите class в атрибутах

config:
actions: ~
fields:
text: { label: 'Текст', attributes: { class: "tinyMCE" } }


еще обсуждение здесь

вторник, июня 16, 2009

Как заставить mysql5 использовать нужный вам default-character и collation

Для начала посмотрите что у вас есть


SHOW VARIABLES LIKE 'character_set%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)


По умолчанию Value=latin1

Теперь вы хотите чтобы все клиенты mysql сразу использовали нужную ва кодировку:
utf8,cp1251 или koi8r

Нужно добавить в файл my.cnf
/etc/mysql/my.cnf

Слудущие переменные:

[client]
default-character-set=utf8
[mysqld]
default-character-set=utf8
default-collation=utf8_general_ci
character-set-server=utf8
init-connect='SET NAMES utf8;'
collation-server=utf8_general_ci
[mysql]
default-character-set=utf8


После изменений перезагружайте сервер.
/etc/init.d/mysql restart

Если не работает еще раз проверьте что у вас происходит:

SHOW VARIABLES LIKE 'character_set%';


проверьте кодировку базы данных:

mysql> show create database yourdatabase;
+-------------+----------------------------------------------------------------------+
| Database | Create Database |
+-------------+----------------------------------------------------------------------+
| yourdatabase | CREATE DATABASE `yourdatabase` /*!40100 DEFAULT CHARACTER SET utf8 */ |
+-------------+----------------------------------------------------------------------+
1 row in set (0.00 sec)


Кодировка по умолчанию для создания таблиц наследуется.

Можно еще проще для ubuntu (10.04):
создать файл, например /etc/mysql/conf.d/mysqld_charset.cnf
с текстом:

[mysqld]
default-character-set=utf8
default-collation=utf8_general_ci
character-set-server=utf8
init-connect='SET NAMES utf8;'
collation-server=utf8_general_ci

[mysql]
default-character-set=utf8


и перезапустить mysql

пятница, мая 08, 2009

Полезные команды для web-разработчика

Искать любое упоминание чего-либо в проекте, исключая системные svn файлы:
grep -Rn 'mysql.php' ./ | grep -v svn

Перенести изменения из одного проекта в другой методом патча:
project1 $ svn diff > filename.patch
project2 $ patch -p0 -i ../project1/filename.patch

Собрать файлы в архив по определенному принципу, в данном случае измененные и добавленные файлы:
perl -e '$res = `svn stat|grep -E "^[AM]"`; $res =~ s/\+|^\w+\s*//gm; $res =~ s/\n/ /g; `tar cfz 1.tgz $res`;'


В буду дальнейшем буду пополнять, есть что то вспомню.

четверг, мая 07, 2009

Google I/O

Как многим разработчикам на Android или просто интерисующимся уже известно, что в 27-28 мая в Сан-франциско пройдет конференция посвященная разработке под Google.Android.
Будет затронуто очень много интересных тем, но самой интересной для меня будет тема посвященная разработке игр.
А именно Chris Pruett расскажет о  Writing Real-Time Games for Android, т.к. я сейчас сам разрабатываю игру, о которой скоро расскажу.
Он болжен будет рассказать о фреймворке, который он разрабатывает специально для разработчиков игр. К тому же расскажет о новых возможностях работы с OpenGLES появившихся в AndroidSDK1.5
Немного поискав я нашел (svn) что-то, что разрабатывает Chris, но не уверен, что это именно тот фреймворк, о котором шла речь.
Если кто "в теме" и нашел этот фреймворк, буду признателен, если скинете ссылку.

вторник, мая 05, 2009

Samsung I7500 будет первым телефоном на Android в России

Пресс-центр корпорации Samsung объявил о примерной дате выхода смартфона на базе Android: 3 квартал 2009 года.
Возникает много вопросов таких как:

  1. Будет ли Android.Market в России?
  2. Сколько будет стоить устройство?
  3. Почему Google позволяет появится первым устройством в России не свой аппарат G1 или G2?


Т.к. в России Samsung пользуется большой популярностью, то ответ на 3 вопрос выглядит логичным.
Если рассуждать дальше, то Google согласен пустить первым Samsung.
К тому же если посмотреть темпы распространения платформы по странам(в прошивке 1.5 уже доступна русская локаль), то скорее всего Android.Market в России в тому времени будет.
А стоить мне кажется он будет не дешевле 20 тыс. руб., чтобы составить конкуренцию iPhone.

суббота, марта 28, 2009

Segmentation fault в php5

Взгляните на этот код:

class A extends B
{
public function getA(){
if($this->getA()){
return $this->getA();
} else {
return $this->getB();
}
}
}

С первого взгляда все верно. Но выдает:
[Sat Mar 28 17:12:59 2009] [notice] child pid 11239 exit signal Segmentation fault (11)

Образуется цикл, тогда надо:

class A extends B
{
public function getA(){
if(parent::getA()){
return parent::getA();
} else {
return $this->getB();
}
}
}

среда, марта 25, 2009

Монтирование(mount) файловой системы по ssh

sudo apt-get install sshfs

sudo sshfs -o allow_other username@hostname.ru:/home/usernamedir /home/usermountdir

Можно создать файл mount.sh и сделать ярлык для запуска на него. Gnome легко определяет что надо ввести пароль для ssh и предлагает сделать это через GUI


вторник, марта 24, 2009

Необходимые компоненты для работы symfony в Ubuntu

Недавно переставил систему с ubuntu 8.10 x86 на ubuntu 9.04 x86_64 на свой рабочий acer aspire 5720G.
Пришлось настраивать систему заново, но так как я сейчас разрабатываю только под symfony. Настраивал работу системы как раз для симфони, поэтому можно рассматривать этот пост как Необходимые компоненты для работы symfony в Ubuntu.
Поставим apache и php5.

sudo apt-get install apache2 php5-mysql libapache2-mod-php5 php5-common mysql-server mysql-common mysql-client-5.0 php5-cli php5-xsl


Устанавливаем кодировку по умолчанию для mysql:

sudo mcedit /etc/mysql/my.cnf
или
sudo nano /etc/mysql/my.cnf

добавить в конец строчки:

default-character-set=utf8
character_set_client=utf8

вместо utf8 можно любую другую, например cp1251

Ставим svn. Если не используете, то можно пропустить.

apt-get install subversion

libapache2-svn ssl-cert libapache2-svn - опционально



Ставим symfony:
sudo mkdir -p /usr/share/php5/symfony-1.2/
sudo svn co http://svn.symfony-project.com/branches/1.2 /usr/share/php5/symfony-1.2/



<VirtualHost 127.0.0.1:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/symfony/web/
<Directory />
Options FollowSymLinks Indexes
AllowOverride All
</Directory>
ErrorLog /var/log/apache2/error.log
LogLevel warn
CustomLog /var/log/apache2/access.log combined
</VirtualHost>


Если проект начинаете с нуля:

wget http://www.symfony-project.org/get/sf_sandbox_1_2.tgz

tar xfz sf_sandbox_1_2.tgz
cp sf_sandbox
mv sf_sandbox/* /var/www/symfony/


Подключаем библиотеку symfony. В файле:
/var/www/symfony/config/ProjectConfiguration.class.php

меняем на:

require_once '/usr/share/php5/symfony-1.2/lib/autoload/sfCoreAutoload.class.php';


Проверяем
cp /var/www/symfony/
./symfony

если все без ошибок, то можно приступать к работе

понедельник, марта 23, 2009

Группировка виджетов(sfWidget) в админ генераторе(admin-generator) для symfony 1.2

Сегодня мне понадобилось динамически стороить элементы формы в админ генераторе и группировать их.
Это оказалось не столь простой задачей.
Я использую symfony 1.2.5-DEV из svn.
Сразу оговорюсь, что строить из embedded forms я не хочу, т.к. элементы динамические.
Вот мои действия. Постоим простейший вариант:

$this->widgetSchema['group0'] = new sfWidgetFormSchema(array(
'name0' => new sfWidgetFormInput(),
'email0' => new sfWidgetFormInput(),
));

сгенерированный html код будет:

<div>
<label for="t_object_key_group0">Group0</label>
<label for="t_object_key_group0_name0">Name0</label>
<input name="t_object_key[group0][name0]" id="t_object_key_group0_name0" type="text">
<label for="t_object_key_group0_email0">Email0</label>
<input name="t_object_key[group0][email0]"id="t_object_key_group0_email0" type="text">
</div>




Смотрится - не очень. Тогда покопавшись в коде я попробовал указать FormFormatter:

$decorator = new sfWidgetFormSchemaFormatterTable($this->widgetSchema
['group0']);
$this->widgetSchema['group0']->addFormFormatter('custom', $decorator);
$this->widgetSchema['group0']->setFormFormatterName('custom');


Эффект тот же.
Тогда я использовал RowFormat:

$this->widgetSchema['group0']->getFormFormatter()->setRowFormat
("\n<table> <th>%label%</th>\n <td>%error%%field%%help%
%hidden_fields
%</td>\n</tr></table>");


сгенерированный html код будет:

<div>
<label for="t_object_key_group0">Group0</label>
<table> <tbody><tr><th><label for="t_object_key_group0_name0"
>Name0</label></th>
<td><input name="t_object_key[group0][name0]"id="t_object_key_group0_name0" type="text"></td>
</tr></tbody></table>
<table> <tbody><tr><th><label
for="t_object_key_group0_email0">Email0</label></th>
<td><input name="t_object_key[group0][email0]" id="t_object_key_group0_email0" type="text"></td>
</tr></tbody></table>
</div>




Тоже не то что я ожидал.
И выходом из всего этого будет:

$this->widgetSchema['group0'] = new sfWidgetFormSchema(array(
'grouped' => new sfWidgetFormSchema(array(
'name0' => new sfWidgetFormInput(),
'email0' => new sfWidgetFormInput(),
))
));
$this->widgetSchema['group0']->getFormFormatter()->setRowFormat
("\n<table> <th>%label%</th>\n <td>%error%%field%%help%
%hidden_fields
%</td>\n</tr></table>");



сгенерированный html код будет:

<div>
<label for="t_object_key_group0">Group0</label>
<table> <tbody><tr><th>Grouped</th>
<td></td></tr><tr>
<th><label for="t_object_key_group0_grouped_name0">Name0</label></th>
<td><input name="t_object_key[group0][grouped][name0]"
id="t_object_key_group0_grouped_name0" type="text"></td>
</tr>
<tr>
<th><label for="t_object_key_group0_grouped_email0">Email0</label></
th>
<td><input name="t_object_key[group0][grouped][email0]"
id="t_object_key_group0_grouped_email0" type="text"></td>
</tr>
</tbody></table>
</div>




Это и есть то что нам надо.
Похоже на то, что setFormFormatterName работает только с embedded forms.
Я пытался попросить объяснений это на гугл групс, но без результатно.

Теперь самое интересное. Как же будет выглядеть валидатор:

$this->validatorSchema['group0'] = new sfValidatorSchema(array(
'grouped' => new sfValidatorSchema(array(
'name0' => new sfValidatorInteger(),
'email0' => new sfValidatorString(),
))
));


Таким образом используя sfWidgetFormSchema можно делать сгруппированные формы любой вложенности.

Интересный момент в mysql 5 для foreign key

CREATE TABLE `key` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(255) NOT NULL,
`slug` varchar(128) NOT NULL,
`group_id` int(11) NOT NULL,
`parent_id` int(11) default '0',
`comp_id` int(11) default NULL,
PRIMARY KEY (`id`),
KEY `key_FI_1` (`group_id`),
KEY `key_FI_2` (`parent_id`),
CONSTRAINT `key_FK_1` FOREIGN KEY (`group_id`) REFERENCES `key_group` (`id`) ON DELETE CASCADE,
CONSTRAINT `key_FK_2` FOREIGN KEY (`parent_id`) REFERENCES `key` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8


С первого взгляда все ок.
Теперь попробуем вставить запись:
insert into key (name,group_id) values ('ключ1',1)

выходит ошибка
mysql>ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`database/key`, CONSTRAINT `key_FK_2` FOREIGN KEY (`parent_id`) REFERENCES `key` (`id`) ON DELETE CASCADE)


Дело оказывается в `parent_id` int(11) default '0' , т.к. нет записи с id=0.
Правильнее сделать `parent_id` int(11) default NULL.

Мне кажется в propel при генерации из схемы можно сделать проверку на defaultValue, т.к. в купе с foreign key это уже ошибочно в большинстве случаев.

воскресенье, марта 22, 2009

Правильное расширение базовых классов Propel в symfony 1.2

Пусть наша модель называется SomeModel, тогда методом для извлечения объекта по строке(slug) будет:

class SomeModelPeer extends BaseSomeModelPeer
{
public static function retrieveBySlug($slug, PropelPDO $con = null){
if (null !== ($obj = SomeModelPeer::getInstanceFromPool((string) $slug))) {
return $obj;
}
if ($con === null) {
$con = Propel::getConnection(self::DATABASE_NAME, Propel::CONNECTION_READ);
}
$criteria = new Criteria();
self::addSelectColumns($criteria);
$criteria->add(self::SLUG,$slug);
$object = self::doSelectOne($criteria,$con);
if($object instanceof SomeModel){
self::addInstanceToPool($object);
}
return $object;
}
}

среда, марта 18, 2009

Как динамически изменить layout для всего application

Иногда нужно менять layout для всего приложения, на пример в празничные дни.
Но писать условия в каждом модуле - не красиво.
Поэтому можно сделать это заранее в фильтре.
В файле app/your_module/config/filters.yml добавь ваш фильтр:
rendering: ~
security: ~

# insert your own filters here

change_layuot:
class: changeLayoutFilter
param:

cache: ~
common: ~
execution: ~


Создайте класс changeLayoutFilter и положите его в lib/:
class changeLayoutFilter extends sfFilter
{
public function execute($filterChain)
{
$request = $this->getContext()->getRequest();
$user = $this->getContext()->getUser();
//устанавливаю layuot
if(isNY()){
sfConfig::set('symfony.view.'.
$this->getContext()->getModuleName().'_'.
$this->getContext()->getActionName().'_layout',
'your_layout');
}
// Execute next filter
$filterChain->execute();
}
}


isNY() - это ваше условие, 'your_layout' - ваш layout.

Кстати, почему я не использовал конструкцию по учебнику:
class changeLayoutFilter extends sfFilter
{
public function execute($filterChain)
{
// Execute this filter only once
if ($this->isFirstCall())
{
[... ...]
}
}
}

?
Потому что если будет использован forward в контроллере(sfAction), то
цепочка фильтров будет запущено заново и наш хак не сработает.
Посмотрите сами в дебагере, вкладка "log" как ваш фильтр будет запущен 2 раза.

вторник, марта 17, 2009

Валидатор телефонного номера в symfony 1.2

Телефонный номер вида: +7(495)1234567

/**
* sfValidatorTelephone
*
* @package
* @subpackage validator
* @author broderix
* @version
*/
class sfValidatorTelephone extends sfValidatorRegex
{
/**
* @see sfValidatorRegex
*/
protected function configure($options = array(), $messages = array())
{
parent::configure($options, $messages);

$this->setOption('pattern', '/^(\+\d\(\d+\)\d*)$/i');
}
}


чтобы заработал положить в lib/

суббота, марта 14, 2009

Упрощение работы с sfWidgetFormSelect в sfForm

Этим постом я начну серию статьёй по symfony 1.2

Иногда необходимо использовать ограниенные списки select в админе в symfony 1.2.
Но не хочется выбирать данные для sfWidgetFormSelect из массива или yaml файла.
Поэтому можно создать свой виджет sfWidgetFormYamlSelect:

class sfWidgetFormYamlSelect extends sfWidgetFormChoice
{
public function __construct($options = array(), $attributes = array())
{
$options['choices'] = new sfCallable(array($this, 'getChoices'));
parent::__construct($options, $attributes);
}
protected function configure($options = array(), $attributes = array())
{
addRequiredOption('form_name', null);
$this->addRequiredOption('field_name', null);
$this->addOption('add_empty', false);
parent::configure($options, $attributes);
}
public function getChoices()
{
$conf_array = sfConfig::get('app_'.$this->getOption('form_name'));
$choices = array();
if(false !== $this->getOption('add_empty')){
$choices[''] = true === $this->getOption('add_empty') ? '' :
$this->getOption('add_empty');
}
$choices = array_merge($choices,$conf_array[$this->getOption('field_name')]);
return $choices;
}
}


В файле config/app.yml (если его нет, то создайте) опишите ваши поля для форм:
all:
.form_settings:
my_form1:
type: { 'yes': 'да', 'no': 'нет }
my_form2:
status: { 0: 'новый', 1: 'подтвержденный', 2: 'редактируется' }



В самой форме это будет выглядеть так:

class MyFormForm1 extends BaseMyForm1Form
{
public function configure()
{
$this->setWidget('status' , new sfWidgetFormYamlSelect(array(
'form_name'=>'my_form1',
'field_name'=>'status'
)));
}
}





Так же это можно использовать в фильтрах.

четверг, января 22, 2009

Обзор шаблонов проектирования

Сегодня наткнулся на хороший обзор шаблонов проектирования. Автор проекта monkeycode.ru периодически выкладывает шаблоны проектирования с краткими пояснениями пока только на языке php5.
Думаю чтобы понять смысл этого достаточно.
Будем следить.
Все шаблоны можно вытащить по тегу шаблоны проектирования.

понедельник, июня 18, 2007

Миграция с MySQL 5.X на Oracle 10.X с обратной совместимостью на платформе php

Этая серия статей направлена на помощь при миграция с MySQL 5.X на Oracle 10.X на платформе php для разработчиков, которые еще не сталкивались СУБД Oracle. Она была написана в ходе собственных проб и ошибок при переносе данных в 700 Мб.
Данную серию статей рассматривайте как рекомендации для новичков, естесственно в них есть удачные и не удачные решения. И тем не мение процесс миграции прошел успешно с поддержкой двух СУБД.

Содержание:

  1. Установка и настройка Oracle
  2. Разработка ПО на php под Oracle 10.X и MySQL
  3. Перенос данных с Mysql на Oracle

суббота, июня 16, 2007

Разработка ПО на php под Oracle 10.X и MySQL

Даты, время

Используйте CURRENT_TIMESTAMP для совместимости с MySQL вместо NOW(), SYSDATE().


Oracle> select CURRENT_TIMESTAMP from dual;
CURRENT_TIMESTAMP
------------------------------------------
14-JUN-07 02.47.52.987970 PM +04:00



mysql> select CURRENT_TIMESTAMP;
+---------------------+
| CURRENT_TIMESTAMP |
+---------------------+
| 2007-06-14 14:58:23 |
+---------------------+
1 row in set (0.00 sec)


Установите стандартное время для MySQL в формате 'YYYY-MM-DD HH24:MI:SS'
Oracle> ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
Session altered.

Старайтесь выкручиваться разными приёмами:
mysql> select hour(timediff(now(), t_timestamp)) from table1;
Oracle> select CEIL(SYSDATE - t_timestamp) from table1;


если неудаётся, то ...
Создайте недостающие функции:

FIND_IN_SET
CREATE OR REPLACE FUNCTION
FIND_IN_SET(TWHAT VARCHAR2, TWHERE VARCHAR2) RETURN INTEGER
IS
BEGIN
DECLARE
CDATA varchar2(4000);
CURSOR TDATA IS
SELECT REGEXP_REPLACE(REGEXP_SUBSTR(TWHERE, ','||TWHAT||',|^'||TWHAT||',|^'||TWHAT||'$|,'||TWHAT||'$'),',','') FROM DUAL;
FROM DUAL;
BEGIN
open TDATA;
FETCH TDATA into CDATA;
close TDATA;
IF (TO_CHAR(CDATA) = TWHAT) THEN
return 1;
ELSE
return 0;
END IF;
END;
END FIND_IN_SET;
/


Тесты:

Oracle> SELECT table1.field1 from table1
WHERE find_in_set('5',table1.field1) >= 1;

table1.field1
-------------
',5,1,2,3,4,'

mysql> SELECT table1.field1 from table1
WHERE find_in_set('5',table1.field1) >= 1;

+---------------+
| table1.field1 |
+---------------+
| ,5,1,2,3,4, |
+---------------+
1 row in set (0.00 sec)


CONCAT_WS

Oracle> CREATE OR REPLACE FUNCTION
CONCAT_WS(PATTERN VARCHAR2,
STR1 VARCHAR2 DEFAULT '',
STR2 VARCHAR2 DEFAULT '',
STR3 VARCHAR2 DEFAULT '',
STR4 VARCHAR2 DEFAULT '',
STR5 VARCHAR2 DEFAULT '',
STR6 VARCHAR2 DEFAULT '',
STR7 VARCHAR2 DEFAULT '',
STR8 VARCHAR2 DEFAULT '',
STR9 VARCHAR2 DEFAULT '',
STR10 VARCHAR2 DEFAULT '',
STR11 VARCHAR2 DEFAULT '',
STR12 VARCHAR2 DEFAULT ''
) RETURN VARCHAR2
IS
BEGIN
DECLARE
CDATA varchar2(4000):='';
BEGIN
IF (LENGTH(STR1) > 0) THEN
CDATA := STR1;
END IF;
IF (LENGTH(STR2) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR2);
END IF;
IF (LENGTH(STR3) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR3);
END IF;
IF (LENGTH(STR4) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR4);
END IF;
IF (LENGTH(STR5) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR5);
END IF;
IF (LENGTH(STR6) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR6);
END IF;
IF (LENGTH(STR7) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR7);
END IF;
IF (LENGTH(STR8) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR8);
END IF;
IF (LENGTH(STR9) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR9);
END IF;
IF (LENGTH(STR10) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR10);
END IF;
IF (LENGTH(STR11) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR11);
END IF;
IF (LENGTH(STR12) > 0) THEN
CDATA := CONCAT(CDATA,PATTERN);
CDATA := CONCAT(CDATA,STR12);
END IF;
return CDATA;
END;
END;
/

Function created.

Тесты:

Oracle> select concat_ws('::','1','2') from dual;
CONCAT_WS('::','1','2')
------------------------------------------------
1::2

Oracle> select concat_ws('::','1') from dual;
CONCAT_WS('::','1')
------------------------------------------------
1

mysql> select concat_ws('::','1','2');
+-------------------------+
| concat_ws('::','1','2') |
+-------------------------+
| 1::2 |
+-------------------------+
1 row in set (0.00 sec)

mysql> select concat_ws('::','1');
+---------------------+
| concat_ws('::','1') |
+---------------------+
| 1 |
+---------------------+
1 row in set (0.00 sec)


Но не всегда получается сделать универсальный код или создать недостающие функции, слишком проблематично или сложно, учитывая временные рамки. Чтобы выкрутится, переложите часть забот с sql на плечи вашего скриптового языка.

PHP
Если множество решений для разработки приложений поддерживающих множество СУБД. Можно пойти:

1) путем обработки запроса регулярными выражениями подаваемого в php-класс под каждую СУБД(пример в исходных кодах phpbb.com, 3-я версия поддерживает mysql, oracle, postgresql).
Преимущества:

  • можно добавить поддержку СУБД на любом этапе разработки;

Недостатки

  • сложность в составлении регулярных выражениях, есть вероятность ошибки, т.е. в равно приходится возвращаться к старому коду;

2) путем обработки запроса перед тем как подать его в php-класс.
Преимущества:

  • надежность кода, вы всегда знаете как будет выполняться тот или иной запрос;

Недостатки

  • для добавления поддержки новой СУБД придется изменять все запросы во всем коде;

Я выбрал 2 способ:


if($config['driver'] == "mysql"){
$db_limit = " LIMIT 1";
} else {
if($['driver'] == "oracle"){
$db_limit = " AND rownum = 1";
}
}
$sql = "
SELECT field1 f1,field2 f2
FROM table1
WHERE f1 = f2 ".$db_limit;

Если запрос сложнее, то необходимо обрабатывать его начиная с блока WHERE:
(заметьте, что в блоке GROUP BY для Oracle приходится перечислять все поля)


if($config['driver'] == "mysql"){
$db_stuff = "
WHERE f1 = f2
GROUP BY field1
ORDER BY field1 DESC
LIMIT 1";
} else {
if($['driver'] == "oracle"){
$db_stuff = "
WHERE f1 = f2 AND rownum = 1
GROUP BY field1,field2
ORDER BY field1 DESC";
}
}
$sql = "
SELECT field1 f1,field2 f2
FROM table1 ".
$db_stuff;

Еще пример:

if($config['driver'] == "mysql"){
$db_hour = "hour(timediff(now(), t_timestamp))";
} else {
if($config['driver'] == "oracle"){
$db_hour = "CEIL(SYSDATE - t_timestamp)";
}
}
$sql = "
SELECT field1 f1,field2 f2, ".
$db_hour." t_hour"
."FROM table1"


ORA ERRORS

ORA-00923: ключевое слово FROM не найдено там, где оно ожидалось.
возникает, если использовать ключевые слова: number, char, date , ...
в конструкциях:

sql> select somename number, somedate date from sometable;

ORA-00933: неверное завершение SQL-предложения

Возникает, если вы использовали посторонние символы.
Как правило это 'AS', Oracle не понимает эту конструкцию:

Oracle> SELECT table1.field1 AS f FROM table1;

Другие статьи:

  1. Установка и настройка Oracle
  2. Разработка ПО на php под Oracle 10.X и MySQL
  3. Перенос данных с Mysql на Oracle