Pages

ラベル ZendFramework の投稿を表示しています。 すべての投稿を表示
ラベル ZendFramework の投稿を表示しています。 すべての投稿を表示

2013年9月26日木曜日

ZendFramework2 toRouteの使い方

/mod/con/act というURLにリダイレクトしたいと思った時の toRoute の1つ目のパラメータ $route の指定方法がちょっと不明だったので、メモ。

最初こんな風に書いてみた。。

return $this->redirect()->toRoute('/mod/con/act');
return $this->redirect()->toRoute('mod/con/act');

ん~ どちらもダメ。。

正解は

return $this->redirect()->toRoute('mod\con', array('action'=>'act'));

'mod\con' これは module.config.php で下記のように定義している。

'mod\con' => array(
    'type'    => 'segment',
    'options' => array(
        'route'    => '/mod/con[/:action]',
        'constraints' => array(
            'action' => '[a-zA-Z][a-zA-Z0-9]*',
        ),
        'defaults' => array(
            'controller' => 'Mod\Controller\Con',
        ),
    ),
),

ZendFramework2 DateTimeSelectを使ってみた3

ん~ なんか色々ハマったけど、何とか動くものができた。

まず標準の DateTimeSelect のままではエラーが出るので、 オリジナルのDateTimeSelectを追加。
と言ってもDateTimeSelectを継承して

  • getHoursOptions
  • getMinutesOptions
  • getSecondsOptions
をオーバーライドしたものです。

\module\Application\config\module.config.php

\module\Application\config\module.config.php

'view_helpers' => array(
    'invokables' => array(
        'formDateTimeSelect2' => 'Application\View\Helper\FormDateTimeSelect2',
    ),
),
を追加する。 次に Application\View\Helper\FormDateTimeSelect2.php を追加する。 中身はこんな感じ。

\module\Application\src\Application\View\Helper\FormDateTimeSelect2.php

namespace Application\View\Helper;

use DateTime;
use IntlDateFormatter;
use Zend\Form\View\Helper\FormDateTimeSelect;

class FormDateTimeSelect2 extends FormDateTimeSelect
{
    protected function getHoursOptions($pattern)
    {
        $keyFormatter   = new IntlDateFormatter($this->getLocale(), null, null, null, null, 'HH');
        $valueFormatter = new IntlDateFormatter($this->getLocale(), null, null, null, null, $pattern);
        $date           = new DateTime('1970-01-01 00:00:00');

        $result = array();
        for ($hour = 1; $hour <= 24; $hour++) {
            $key   = $keyFormatter->format($date->getTimestamp());
            $value = $valueFormatter->format($date->getTimestamp());
            $result[$key] = $value;

            $date->modify('+1 hour');
        }
        return $result;
    }
    protected function getMinutesOptions($pattern)
    {
        $keyFormatter   = new IntlDateFormatter($this->getLocale(), null, null, null, null, 'mm');
        $valueFormatter = new IntlDateFormatter($this->getLocale(), null, null, null, null, $pattern);
        $date           = new DateTime('1970-01-01 00:00:00');

        $result = array();
        for ($min = 1; $min <= 60; $min++) {
            $key   = $keyFormatter->format($date->getTimestamp());
            $value = $valueFormatter->format($date->getTimestamp());
            $result[$key] = $value;

            $date->modify('+1 minute');
        }

        return $result;
    }
    protected function getSecondsOptions($pattern)
    {
        $keyFormatter   = new IntlDateFormatter($this->getLocale(), null, null, null, null, 'ss');
        $valueFormatter = new IntlDateFormatter($this->getLocale(), null, null, null, null, $pattern);
        $date           = new DateTime('1970-01-01 00:00:00');

        $result = array();
        for ($sec = 1; $sec <= 60; $sec++) {
            $key   = $keyFormatter->format($date->getTimestamp());
            $value = $valueFormatter->format($date->getTimestamp());
            $result[$key] = $value;

            $date->modify('+1 second');
        }

        return $result;
    }
}
phtmlファイルでは下記のように記述

formDateTimeSelect2($form->get('date_from'), IntlDateFormatter::SHORT, IntlDateFormatter::LONG, 'ja'); ?>
これで動くはずです。

余談ですが、サンプルのAlbumモジュールのように実装してもvalidatorのところが上手く動作しません。
\Zend\Form\Element\DateTimeSelect.php::getInputSpecification
を見ると解るんですが、下記のようにフィルターでコールバックを追加してStringに変換しなければなりません。
そして、ValidatorでDateでチェックするようです。


protected function getValidator()
{
    if (null === $this->validator) {
        $this->validator = new DateValidator(array('format' => 'Y-m-d H:i:s'));
    }

    return $this->validator;
}

public function getInputSpecification()
{
    return array(
        'name' => $this->getName(),
        'required' => false,
        'filters' => array(
            array(
                'name'    => 'Callback',
                'options' => array(
                    'callback' => function($date) {
                        // Convert the date to a specific format
                        if (is_array($date)) {
                            if (!isset($date['second'])) {
                                $date['second'] = '00';
                            }
                            $date = sprintf('%s-%s-%s %s:%s:%s',
                                $date['year'], $date['month'], $date['day'],
                                $date['hour'], $date['minute'], $date['second']
                            );
                        }

                        return $date;
                    }
                )
            )
        ),
        'validators' => array(
            $this->getValidator(),
        )
    );
}
これをデフォルトとしてFormオブジェクト作成時にセットしたいんですが、ちょっとやり方が解りませんでした。。

そこで、アクション内で

\module\Album\src\Album\Controller\IndexController.php

if ($request->isPost()) {
    $album= new Album();
    $form->setInputFilter($album->getInputFilter($form->getInputFilter()));
    $form->setData($request->getPost());
...

\module\Album\src\Album\Model\Album.php

public function getInputFilter(InputFilterInterface $defaultInputFilter = NULL)
{
    if (!$this->inputFilter) {
        if (is_null($defaultInputFilter)) {
            $inputFilter = new InputFilter();
        } else {
            $inputFilter = $defaultInputFilter;
        }
    }
    $factory     = new InputFactory();
という感じにしました。

ZendFramework2 DateTimeSelectを使ってみた2

DateTimeSelect が使えない問題が解決しました。

問題はレンダリング時にありました。

ファイルは
\Zend\Form\View\Helper\FormDateTimeSelect.php

こいつの最後の方にある
  • getHoursOptions
  • getMinutesOptions
  • getSecondsOptions
これです。

$date           = new DateTime('1970-01-01 00:00:00');
$key   = $keyFormatter->format($date);

途中略してますが、こんな感じで IntlDateFormatter::format のパラメータに DateTime オブジェクトを指定しているんです。

この format メソッドに DateTime オブジェクトを指定できるのは
変更履歴を見ると解るんですが、5.3.4から対応している。

そして、利用しているサーバーの intl のバージョンを確認。

# yum list install php-intl

すると結果は

php-intl.x86_64  5.3.3-23.el6_4  @updates

ってな感じで5.3.3でした。。
なので、エラーが出て時、分、秒が出てない状況だっというわけです。。

RHEL7 では5.4か5.5が標準パッケージで入りそうなので、それを利用できれば解決しそうですが、実際に利用されるまでにはあと1年以上はかかる気がするので、 intl 5.3.3 で動く方法を探すしかないですね。。

2013年9月24日火曜日

ZendFramework2 DateTimeSelectを使ってみた1

DateSelectを使ってみたんやけど、DateTimeSelectの存在を知ったのでついでに使ってみた。
でも。。

時、分のセレクトエレメントは出力されるけど、optionが空でセットされてない。。
なぜ。。?

 しかもTimeはあるけどTimeSelectは無いんですね。。

2013年9月20日金曜日

ZendFramework2 新しく追加された日付エレメント DateSelect を使ってみた

ZF2で追加された日付エレメント

オプションは以下のように指定。 月の日本語化は /module/Application/language 内の ja_JP.po に追記してmoファイルを作成、上書き。
$this->add(array(
    'name' => 'birthday',
    'type' => 'DateSelect',
    'options' => array(
        'create_empty_option' => false,
        'max_year' => date(Y) + 1,
        'min_year' => '2013',
        'year_attributes' => array(
            'class' => 'span2',
            'value' => date('Y'),
        ),
        'month_attributes' => array(
            'class' => 'span1',
            'value' => date('m'),
        ),
        'day_attributes' => array(
            'class' => 'span1',
            'value' => date('d'),
        ),
        'month_values' => array('1' => '11'),
        'format' => 'Y-m-d',
    ),
));
しかし。。 無い。。 これ出力すると 月、日、年 という国際仕様(?)となる。。 指定する方法が見当たらない。。 仕方ないので、 View側でやってみると
formElement($form->get('birthday')); ?>
formElement($form->get('birthday')->getYearElement()); ?>
formElement($form->get('birthday')->getMonthElement()); ?>
formElement($form->get('birthday')->getDayElement()); ?>
だと出るけど
formElement($form->get('birthday')->getYearElement()); ?>
formElement($form->get('birthday')->getMonthElement()); ?>
formElement($form->get('birthday')->getDayElement()); ?>
だと中身が空っぽ。。 使い方間違ったのか対応していないのか。。 今日はここまで。 誰か知ってたら教えてください。。

2013年9月19日木曜日

ZendFramework2 LIKE句の指定が可能になった

以前は対応していなかったLIKE検索がついに対応してくれてる~~。
tableGateway = $tableGateway;
    }

    public function select()
    {
        $select = $this->tableGateway->getSql()->select();
        $select->where->like('col1', '%foo%');
    }
...
でも。。 LIKE用のエスケープが無い。。。。 2時間探しても見つけられない。。。 そんな事は無いだろう。。。 でも無い。。。 そこは自作って事なのか。。

2012年11月13日火曜日

Zend Framework2 の勉強を始めた

Zend Framework2 の勉強を最近始めたがどうもスムーズに進まない。。
まず日本語マニュアルが無い。。 書籍がない。。(笑)

というのはこの業界じゃよくある事なので、諦めるとしてまずはSkeleton(サンプルみたいなもの)が配布されているので、そこから勉強する事になる。

このSkeletonが自分にとってはなかなか曲者。。

Zend Framework1の時にあったQuick Start(サンプルみたいなもの)と作り方が全然違う。。
コントローラーまでの流れ(ルーター?)に変更があるのは良いとして、Validatorの記述場所などがどうもしっくりこない。。

なんでAlbum.phpに記述なんだろう。。 自分的にはFormの方がしっくり来るんだけどな。。
デコレーターの使い方もかなり変わってるみたいや。。

今月中に一通り理解できるかな。。

2011年8月23日火曜日

Zend_Form エラー文言で項目名を表示させたい

ん~ どうもZend_Formってやつはこういうところですぐにつまづくな。。

とりあえず正規の方法でできる事を調査してみた。

結果としては、Zend_TranslatorをElement単位で指定してやるしかなさそうでした。
%value% みたいにElementのnameをZend_Validateに渡せたら文句無いのにな。。

以下例です。(試してないので間違ってたらゴメンなさい)

$translate = new Zend_Translate(
  array(
    'adapter' => 'array',
    'content' => '/path/to/titleElement.php',
    'locale'  => 'de',
    'delimiter' => ':'
  )
);


$form->getElement('title')->setTranslator($translate )
titleElement.csv の中はこんな感じ?
return array(

    "Value is required and can't be empty" => "タイトルは必須です。",
);

2011年3月18日金曜日

PHP Zend_Formに動的な値を渡す方法

これがなかなかしっくりこなかった。。
やっと解った!!


やっぱりインスタンスを作成する時に渡すんだった。

まずはZend_Formを継承したクラスに受け取る用のmethodを追加する。

class MemberController extends Zend_Controller_Action
{
protected $_foo;

public function init ()
{
$this->addElement('text', 'bar', array(
'validators' => array(
array(
'Db_NoRecordExists',
true,
array(
'foo',
'email',
array('foo_id', $this->_foo))
),
),
));
}

public fucntion setFoo ($foo)
{
$this->_foo = $foo;
}
}


そしてこんな感じで渡してあげる。
これでスッキリ完璧♪


$foo = '1234';
$form = new Application_Form_Foo(array('foo' => $foo);


ちなみに以下は予約されているので、関数名が重複しないように注意。

'Options', 'Config', 'PluginLoader', 'SubForms', 'Translator','Attrib', 'Default'

2010年7月22日木曜日

Zend_Filter の StringTrimで全角スペースを指定するとうまく動作しない

class Zend_Filter_StringTrim

にある

protected function _unicodeTrim($value, $charlist = '\\\\s')

というメソッドがあるんですが、問題はココ

return preg_replace("/$pattern/sSD", '', $value);

これはPHPでUTF-8で動作させている場合に発生する現象らしいのですが、対処方法は

return preg_replace("/$pattern/sSDu", '', $value);

と、パターン修飾子に「u」を追加してあげるだけ。

メソッドにエンコードを指定するのが用意されてるのかと思ったけでそうでもないようですね。。。

2010年4月11日日曜日

Zend_Db_Adapter_Pdo_Odbc 不具合修正しました。

昨日更新した内容では「':シングルクォート」のエスケープ処理が間違っていたので、そこらへんを修正。

/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Db
 * @subpackage Adapter
 * @copyright  Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 *
 */


/**
 * Zend_Db_Adapter_Pdo_Abstract
 */
require_once 'Zend/Db/Adapter/Pdo/Abstract.php';


/**
 * Class for connecting to ODBC System databases and performing common operations.
 *
 * @category   Zend
 * @package    Zend_Db
 * @subpackage Adapter
 * @copyright  Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Default_Plugin_Zend_Db_Adapter_Pdo_Odbc extends Zend_Db_Adapter_Pdo_Abstract
{
    /**
     * PDO type.
     *
     * @var string
     */
    protected $_pdoType = 'odbc';

    protected $_rowCount;

    /**
     * Keys are UPPERCASE SQL datatypes or the constants
     * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
     *
     * Values are:
     * 0 = 32-bit integer
     * 1 = 64-bit integer
     * 2 = float or decimal
     *
     * @var array Associative array of datatypes to values 0, 1, or 2.
     */
    protected $_numericDataTypes = array(
        Zend_Db::INT_TYPE    => Zend_Db::INT_TYPE,
        Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
        Zend_Db::FLOAT_TYPE  => Zend_Db::FLOAT_TYPE,
        'INT'                => Zend_Db::INT_TYPE,
        'SMALLINT'           => Zend_Db::INT_TYPE,
        'TINYINT'            => Zend_Db::INT_TYPE,
        'BIGINT'             => Zend_Db::BIGINT_TYPE,
        'DECIMAL'            => Zend_Db::FLOAT_TYPE,
        'FLOAT'              => Zend_Db::FLOAT_TYPE,
        'MONEY'              => Zend_Db::FLOAT_TYPE,
        'NUMERIC'            => Zend_Db::FLOAT_TYPE,
        'REAL'               => Zend_Db::FLOAT_TYPE,
        'SMALLMONEY'         => Zend_Db::FLOAT_TYPE
    );

    /**
     * Creates a PDO DSN for the adapter from $this->_config settings.
     *
     * @return string
     */
    protected function _dsn()
    {
        // baseline of DSN parts
        $dsn = $this->_config;

        // don't pass the username and password in the DSN
        unset($dsn['username']);
        unset($dsn['password']);
        unset($dsn['driver_options']);
        unset($dsn['port']);

        // this driver supports multiple DSN prefixes
        // @see http://www.php.net/manual/en/ref.pdo-dblib.connection.php
        if (isset($dsn['pdoType'])) {
            switch (strtolower($dsn['pdoType'])) {
                default:
                    $this->_pdoType = 'odbc';
                    break;
            }
            unset($dsn['pdoType']);
        }

        if (isset($dsn['dbname'])) {
            $dsn = $this->_pdoType . ':' . $dsn['dbname'];
        } else {
            // use all remaining parts in the DSN
            foreach ($dsn as $key => $val) {
                $dsn[$key] = "$key=$val";
            }

            $dsn = $this->_pdoType . ':' . implode(';', $dsn);
        }
        return $dsn;
    }

    /**
     * @return void
     */
    protected function _connect()
    {
        if ($this->_connection) {
            return;
        }
        parent::_connect();
        $this->_connection->exec('SET QUOTED_IDENTIFIER ON');
    }

    /**
     * Begin a transaction.
     *
     * It is necessary to override the abstract PDO transaction functions here, as
     * the PDO driver for MSSQL does not support transactions.
     */
    protected function _beginTransaction()
    {
        $this->_connect();
        $this->_connection->exec('BEGIN TRANSACTION');
        return true;
    }

    /**
     * Commit a transaction.
     *
     * It is necessary to override the abstract PDO transaction functions here, as
     * the PDO driver for MSSQL does not support transactions.
     */
    protected function _commit()
    {
        $this->_connect();
        $this->_connection->exec('COMMIT TRANSACTION');
        return true;
    }

    /**
     * Roll-back a transaction.
     *
     * It is necessary to override the abstract PDO transaction functions here, as
     * the PDO driver for MSSQL does not support transactions.
     */
    protected function _rollBack() {
        $this->_connect();
        $this->_connection->exec('ROLLBACK TRANSACTION');
        return true;
    }

    /**
     * Returns a list of the tables in the database.
     *
     * @return array
     */
    public function listTables()
    {
        $sql = "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name";
        return $this->fetchCol($sql);
    }

    /**
     * Returns the column descriptions for a table.
     *
     * The return value is an associative array keyed by the column name,
     * as returned by the RDBMS.
     *
     * The value of each array element is an associative array
     * with the following keys:
     *
     * SCHEMA_NAME      => string; name of database or schema
     * TABLE_NAME       => string;
     * COLUMN_NAME      => string; column name
     * COLUMN_POSITION  => number; ordinal position of column in table
     * DATA_TYPE        => string; SQL datatype name of column
     * DEFAULT          => string; default expression of column, null if none
     * NULLABLE         => boolean; true if column can have nulls
     * LENGTH           => number; length of CHAR/VARCHAR
     * SCALE            => number; scale of NUMERIC/DECIMAL
     * PRECISION        => number; precision of NUMERIC/DECIMAL
     * UNSIGNED         => boolean; unsigned property of an integer type
     * PRIMARY          => boolean; true if column is part of the primary key
     * PRIMARY_POSITION => integer; position of column in primary key
     * PRIMARY_AUTO     => integer; position of auto-generated column in primary key
     *
     * @todo Discover column primary key position.
     * @todo Discover integer unsigned property.
     *
     * @param string $tableName
     * @param string $schemaName OPTIONAL
     * @return array
     */
    public function describeTable($tableName, $schemaName = null)
    {
        if ($schemaName != null) {
            if (strpos($schemaName, '.') !== false) {
                $result = explode('.', $schemaName);
                $schemaName = $result[1];
            }
        }
        /**
         * Discover metadata information about this table.
         */
        $sql = "exec sp_columns @table_name = " . $this->quoteIdentifier($tableName, true);
        if ($schemaName != null) {
            $sql .= ", @table_owner = " . $this->quoteIdentifier($schemaName, true);
        }

        $stmt = $this->query($sql);
        $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
        $stmt->closeCursor();

        $table_name  = 2;
        $column_name = 3;
        $type_name   = 5;
        $precision   = 6;
        $length      = 7;
        $scale       = 8;
        $nullable    = 10;
        $column_def  = 12;
        $column_position = 16;

        /**
         * Discover primary key column(s) for this table.
         */
        $sql = "exec sp_pkeys @table_name = " . $this->quoteIdentifier($tableName, true);
        if ($schemaName != null) {
            $sql .= ", @table_owner = " . $this->quoteIdentifier($schemaName, true);
        }

        $stmt = $this->query($sql);
        $primaryKeysResult = $stmt->fetchAll(Zend_Db::FETCH_NUM);
        $stmt->closeCursor();
        $pkey_column_name = 3;
        $pkey_key_seq = 4;
        foreach ($primaryKeysResult as $pkeysRow) {
            $primaryKeyColumn[$pkeysRow[$pkey_column_name]] = $pkeysRow[$pkey_key_seq];
        }

        $desc = array();
        $p = 1;
        foreach ($result as $key => $row) {
            $identity = false;
            $words = explode(' ', $row[$type_name], 2);
            if (isset($words[0])) {
                $type = $words[0];
                if (isset($words[1])) {
                    $identity = (bool) preg_match('/identity/', $words[1]);
                }
            }

            $isPrimary = array_key_exists($row[$column_name], $primaryKeyColumn);
            if ($isPrimary) {
                $primaryPosition = $primaryKeyColumn[$row[$column_name]];
            } else {
                $primaryPosition = null;
            }

            $desc[$this->foldCase($row[$column_name])] = array(
                'SCHEMA_NAME'      => null, // @todo
                'TABLE_NAME'       => $this->foldCase($row[$table_name]),
                'COLUMN_NAME'      => $this->foldCase($row[$column_name]),
                'COLUMN_POSITION'  => (int) $row[$column_position],
                'DATA_TYPE'        => $type,
                'DEFAULT'          => $row[$column_def],
                'NULLABLE'         => (bool) $row[$nullable],
                'LENGTH'           => $row[$length],
                'SCALE'            => $row[$scale],
                'PRECISION'        => $row[$precision],
                'UNSIGNED'         => null, // @todo
                'PRIMARY'          => $isPrimary,
                'PRIMARY_POSITION' => $primaryPosition,
                'IDENTITY'         => $identity
            );
        }
        return $desc;
    }

    /**
     * Adds an adapter-specific LIMIT clause to the SELECT statement.
     *
     * @link http://lists.bestpractical.com/pipermail/rt-devel/2005-June/007339.html
     *
     * @param string $sql
     * @param integer $count
     * @param integer $offset OPTIONAL
     * @return string
     */
     public function limit($sql, $count, $offset = 0)
     {

        $count = intval($count);
        if ($count <= 0) {
            /** @see Zend_Db_Adapter_Exception */
            require_once 'Zend/Db/Adapter/Exception.php';
            throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
        }

        $offset = intval($offset);
        if ($offset < 0) {
            /** @see Zend_Db_Adapter_Exception */
            require_once 'Zend/Db/Adapter/Exception.php';
            throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
        }

        $sql = preg_replace(
            '/^SELECT\s+(DISTINCT\s)?/i',
            'SELECT $1TOP ' . ($count+$offset) . ' ',
            $sql
            );

        if ($offset > 0) {
            $orderby = stristr($sql, 'ORDER BY');

            if ($orderby !== false) {
                $orderParts = explode(',', substr($orderby, 8));
                $pregReplaceCount = null;
                $orderbyInverseParts = array();
                foreach ($orderParts as $orderPart) {
                    $orderPart = rtrim($orderPart);
                    $inv = preg_replace('/\s+desc$/i', ' ASC', $orderPart, 1, $pregReplaceCount);
                    if ($pregReplaceCount) {
                        $orderbyInverseParts[] = $inv;
                        continue;
                    }
                    $inv = preg_replace('/\s+asc$/i', ' DESC', $orderPart, 1, $pregReplaceCount);
                    if ($pregReplaceCount) {
                        $orderbyInverseParts[] = $inv;
                        continue;
                    } else {
                        $orderbyInverseParts[] = $orderPart . ' DESC';
                    }
                }

                $orderbyInverse = 'ORDER BY ' . implode(', ', $orderbyInverseParts);
            }

            if (!is_null($this->getRowCount()) && ($count+$offset) > $this->getRowCount())
                $count = $this->getRowCount() - $offset;

            $sql = 'SELECT * FROM (SELECT TOP ' . $count . ' * FROM (' . $sql . ') AS inner_tbl';
            if ($orderby !== false) {
                $sql .= ' ' . $orderbyInverse . ' ';
            }
            $sql .= ') AS outer_tbl';
            if ($orderby !== false) {
                $sql .= ' ' . $orderby;
            }
        }

        return $sql;
    }

    /**
     * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
     *
     * As a convention, on RDBMS brands that support sequences
     * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
     * from the arguments and returns the last id generated by that sequence.
     * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
     * returns the last value generated for such a column, and the table name
     * argument is disregarded.
     *
     * Microsoft SQL Server does not support sequences, so the arguments to
     * this method are ignored.
     *
     * @param string $tableName   OPTIONAL Name of table.
     * @param string $primaryKey  OPTIONAL Name of primary key column.
     * @return string
     * @throws Zend_Db_Adapter_Exception
     */
    public function lastInsertId($tableName = null, $primaryKey = null)
    {
        $sql = 'SELECT SCOPE_IDENTITY()';
        return (int)$this->fetchOne($sql);
    }

    public function getServerVersion()
    {
        try {
            $stmt = $this->query("SELECT SERVERPROPERTY('productversion')");
            $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
            if (count($result)) {
                return $result[0][0];
            }
            return null;
        } catch (PDOException $e) {
            return null;
        }
    }

    protected function _quote($value)
    {
        if (is_int($value)) {
            return $value;
        } elseif (is_float($value)) {
            return sprintf('%F', $value);
        }
        /*
         * Original
         * return "'" . addcslashes($value, "\000\n\r\\'\"\032") . "'";
        */
        $value = str_replace("'", "''", $value);
        return "'" . addcslashes($value, "\000\n\r\032") . "'";
    }

    public function getRowCount()
    {
        return $this->_rowCount;
    }

    public function setRowCount($count)
    {
        $this->_rowCount = $count;
    }
}

2010年4月10日土曜日

Zend_Db_Adapter_Pdo_Odbc 不具合修正しました。

quoteをオーバーライドしているところで問題があったのを修正。
っていうか  addslashes に第2パラメーターが存在したとは。。。

/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Db
 * @subpackage Adapter
 * @copyright  Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 *
 */


/**
 * Zend_Db_Adapter_Pdo_Abstract
 */
require_once 'Zend/Db/Adapter/Pdo/Abstract.php';


/**
 * Class for connecting to ODBC System databases and performing common operations.
 *
 * @category   Zend
 * @package    Zend_Db
 * @subpackage Adapter
 * @copyright  Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_Db_Adapter_Pdo_Odbc extends Zend_Db_Adapter_Pdo_Abstract
{
    /**
     * PDO type.
     *
     * @var string
     */
    protected $_pdoType = 'odbc';

    protected $_rowCount;

    /**
     * Keys are UPPERCASE SQL datatypes or the constants
     * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
     *
     * Values are:
     * 0 = 32-bit integer
     * 1 = 64-bit integer
     * 2 = float or decimal
     *
     * @var array Associative array of datatypes to values 0, 1, or 2.
     */
    protected $_numericDataTypes = array(
        Zend_Db::INT_TYPE    => Zend_Db::INT_TYPE,
        Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
        Zend_Db::FLOAT_TYPE  => Zend_Db::FLOAT_TYPE,
        'INT'                => Zend_Db::INT_TYPE,
        'SMALLINT'           => Zend_Db::INT_TYPE,
        'TINYINT'            => Zend_Db::INT_TYPE,
        'BIGINT'             => Zend_Db::BIGINT_TYPE,
        'DECIMAL'            => Zend_Db::FLOAT_TYPE,
        'FLOAT'              => Zend_Db::FLOAT_TYPE,
        'MONEY'              => Zend_Db::FLOAT_TYPE,
        'NUMERIC'            => Zend_Db::FLOAT_TYPE,
        'REAL'               => Zend_Db::FLOAT_TYPE,
        'SMALLMONEY'         => Zend_Db::FLOAT_TYPE
    );

    /**
     * Creates a PDO DSN for the adapter from $this->_config settings.
     *
     * @return string
     */
    protected function _dsn()
    {
        // baseline of DSN parts
        $dsn = $this->_config;

        // don't pass the username and password in the DSN
        unset($dsn['username']);
        unset($dsn['password']);
        unset($dsn['driver_options']);
        unset($dsn['port']);

        // this driver supports multiple DSN prefixes
        // @see http://www.php.net/manual/en/ref.pdo-dblib.connection.php
        if (isset($dsn['pdoType'])) {
            switch (strtolower($dsn['pdoType'])) {
                default:
                    $this->_pdoType = 'odbc';
                    break;
            }
            unset($dsn['pdoType']);
        }

        if (isset($dsn['dbname'])) {
            $dsn = $this->_pdoType . ':' . $dsn['dbname'];
        } else {
            // use all remaining parts in the DSN
            foreach ($dsn as $key => $val) {
                $dsn[$key] = "$key=$val";
            }

            $dsn = $this->_pdoType . ':' . implode(';', $dsn);
        }
        return $dsn;
    }

    /**
     * @return void
     */
    protected function _connect()
    {
        if ($this->_connection) {
            return;
        }
        parent::_connect();
        $this->_connection->exec('SET QUOTED_IDENTIFIER ON');
    }

    /**
     * Begin a transaction.
     *
     * It is necessary to override the abstract PDO transaction functions here, as
     * the PDO driver for MSSQL does not support transactions.
     */
    protected function _beginTransaction()
    {
        $this->_connect();
        $this->_connection->exec('BEGIN TRANSACTION');
        return true;
    }

    /**
     * Commit a transaction.
     *
     * It is necessary to override the abstract PDO transaction functions here, as
     * the PDO driver for MSSQL does not support transactions.
     */
    protected function _commit()
    {
        $this->_connect();
        $this->_connection->exec('COMMIT TRANSACTION');
        return true;
    }

    /**
     * Roll-back a transaction.
     *
     * It is necessary to override the abstract PDO transaction functions here, as
     * the PDO driver for MSSQL does not support transactions.
     */
    protected function _rollBack() {
        $this->_connect();
        $this->_connection->exec('ROLLBACK TRANSACTION');
        return true;
    }

    /**
     * Returns a list of the tables in the database.
     *
     * @return array
     */
    public function listTables()
    {
        $sql = "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name";
        return $this->fetchCol($sql);
    }

    /**
     * Returns the column descriptions for a table.
     *
     * The return value is an associative array keyed by the column name,
     * as returned by the RDBMS.
     *
     * The value of each array element is an associative array
     * with the following keys:
     *
     * SCHEMA_NAME      => string; name of database or schema
     * TABLE_NAME       => string;
     * COLUMN_NAME      => string; column name
     * COLUMN_POSITION  => number; ordinal position of column in table
     * DATA_TYPE        => string; SQL datatype name of column
     * DEFAULT          => string; default expression of column, null if none
     * NULLABLE         => boolean; true if column can have nulls
     * LENGTH           => number; length of CHAR/VARCHAR
     * SCALE            => number; scale of NUMERIC/DECIMAL
     * PRECISION        => number; precision of NUMERIC/DECIMAL
     * UNSIGNED         => boolean; unsigned property of an integer type
     * PRIMARY          => boolean; true if column is part of the primary key
     * PRIMARY_POSITION => integer; position of column in primary key
     * PRIMARY_AUTO     => integer; position of auto-generated column in primary key
     *
     * @todo Discover column primary key position.
     * @todo Discover integer unsigned property.
     *
     * @param string $tableName
     * @param string $schemaName OPTIONAL
     * @return array
     */
    public function describeTable($tableName, $schemaName = null)
    {
        if ($schemaName != null) {
            if (strpos($schemaName, '.') !== false) {
                $result = explode('.', $schemaName);
                $schemaName = $result[1];
            }
        }
        /**
         * Discover metadata information about this table.
         */
        $sql = "exec sp_columns @table_name = " . $this->quoteIdentifier($tableName, true);
        if ($schemaName != null) {
            $sql .= ", @table_owner = " . $this->quoteIdentifier($schemaName, true);
        }

        $stmt = $this->query($sql);
        $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
        $stmt->closeCursor();

        $table_name  = 2;
        $column_name = 3;
        $type_name   = 5;
        $precision   = 6;
        $length      = 7;
        $scale       = 8;
        $nullable    = 10;
        $column_def  = 12;
        $column_position = 16;

        /**
         * Discover primary key column(s) for this table.
         */
        $sql = "exec sp_pkeys @table_name = " . $this->quoteIdentifier($tableName, true);
        if ($schemaName != null) {
            $sql .= ", @table_owner = " . $this->quoteIdentifier($schemaName, true);
        }

        $stmt = $this->query($sql);
        $primaryKeysResult = $stmt->fetchAll(Zend_Db::FETCH_NUM);
        $stmt->closeCursor();
        $pkey_column_name = 3;
        $pkey_key_seq = 4;
        foreach ($primaryKeysResult as $pkeysRow) {
            $primaryKeyColumn[$pkeysRow[$pkey_column_name]] = $pkeysRow[$pkey_key_seq];
        }

        $desc = array();
        $p = 1;
        foreach ($result as $key => $row) {
            $identity = false;
            $words = explode(' ', $row[$type_name], 2);
            if (isset($words[0])) {
                $type = $words[0];
                if (isset($words[1])) {
                    $identity = (bool) preg_match('/identity/', $words[1]);
                }
            }

            $isPrimary = array_key_exists($row[$column_name], $primaryKeyColumn);
            if ($isPrimary) {
                $primaryPosition = $primaryKeyColumn[$row[$column_name]];
            } else {
                $primaryPosition = null;
            }

            $desc[$this->foldCase($row[$column_name])] = array(
                'SCHEMA_NAME'      => null, // @todo
                'TABLE_NAME'       => $this->foldCase($row[$table_name]),
                'COLUMN_NAME'      => $this->foldCase($row[$column_name]),
                'COLUMN_POSITION'  => (int) $row[$column_position],
                'DATA_TYPE'        => $type,
                'DEFAULT'          => $row[$column_def],
                'NULLABLE'         => (bool) $row[$nullable],
                'LENGTH'           => $row[$length],
                'SCALE'            => $row[$scale],
                'PRECISION'        => $row[$precision],
                'UNSIGNED'         => null, // @todo
                'PRIMARY'          => $isPrimary,
                'PRIMARY_POSITION' => $primaryPosition,
                'IDENTITY'         => $identity
            );
        }
        return $desc;
    }

    /**
     * Adds an adapter-specific LIMIT clause to the SELECT statement.
     *
     * @link http://lists.bestpractical.com/pipermail/rt-devel/2005-June/007339.html
     *
     * @param string $sql
     * @param integer $count
     * @param integer $offset OPTIONAL
     * @return string
     */
     public function limit($sql, $count, $offset = 0)
     {

        $count = intval($count);
        if ($count <= 0) {
            /** @see Zend_Db_Adapter_Exception */
            require_once 'Zend/Db/Adapter/Exception.php';
            throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
        }

        $offset = intval($offset);
        if ($offset < 0) {
            /** @see Zend_Db_Adapter_Exception */
            require_once 'Zend/Db/Adapter/Exception.php';
            throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
        }

        $sql = preg_replace(
            '/^SELECT\s+(DISTINCT\s)?/i',
            'SELECT $1TOP ' . ($count+$offset) . ' ',
            $sql
            );

        if ($offset > 0) {
            $orderby = stristr($sql, 'ORDER BY');

            if ($orderby !== false) {
                $orderParts = explode(',', substr($orderby, 8));
                $pregReplaceCount = null;
                $orderbyInverseParts = array();
                foreach ($orderParts as $orderPart) {
                    $orderPart = rtrim($orderPart);
                    $inv = preg_replace('/\s+desc$/i', ' ASC', $orderPart, 1, $pregReplaceCount);
                    if ($pregReplaceCount) {
                        $orderbyInverseParts[] = $inv;
                        continue;
                    }
                    $inv = preg_replace('/\s+asc$/i', ' DESC', $orderPart, 1, $pregReplaceCount);
                    if ($pregReplaceCount) {
                        $orderbyInverseParts[] = $inv;
                        continue;
                    } else {
                        $orderbyInverseParts[] = $orderPart . ' DESC';
                    }
                }

                $orderbyInverse = 'ORDER BY ' . implode(', ', $orderbyInverseParts);
            }

            if (!is_null($this->getRowCount()) && ($count+$offset) > $this->getRowCount())
                $count = $this->getRowCount() - $offset;

            $sql = 'SELECT * FROM (SELECT TOP ' . $count . ' * FROM (' . $sql . ') AS inner_tbl';
            if ($orderby !== false) {
                $sql .= ' ' . $orderbyInverse . ' ';
            }
            $sql .= ') AS outer_tbl';
            if ($orderby !== false) {
                $sql .= ' ' . $orderby;
            }
        }

        return $sql;
    }

    /**
     * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
     *
     * As a convention, on RDBMS brands that support sequences
     * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
     * from the arguments and returns the last id generated by that sequence.
     * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
     * returns the last value generated for such a column, and the table name
     * argument is disregarded.
     *
     * Microsoft SQL Server does not support sequences, so the arguments to
     * this method are ignored.
     *
     * @param string $tableName   OPTIONAL Name of table.
     * @param string $primaryKey  OPTIONAL Name of primary key column.
     * @return string
     * @throws Zend_Db_Adapter_Exception
     */
    public function lastInsertId($tableName = null, $primaryKey = null)
    {
        $sql = 'SELECT SCOPE_IDENTITY()';
        return (int)$this->fetchOne($sql);
    }

    public function getServerVersion()
    {
        try {
            $stmt = $this->query("SELECT SERVERPROPERTY('productversion')");
            $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
            if (count($result)) {
                return $result[0][0];
            }
            return null;
        } catch (PDOException $e) {
            return null;
        }
    }

    protected function _quote($value)
    {
        if (is_int($value)) {
            return $value;
        } elseif (is_float($value)) {
            return sprintf('%F', $value);
        }
        /*
         * Original
         * return "'" . addcslashes($value, "\000\n\r\\'\"\032") . "'";
        */
        return "'" . addcslashes($value, "\000\n\r'\"\032") . "'";
    }

    public function getRowCount()
    {
        return $this->_rowCount;
    }

    public function setRowCount($count)
    {
        $this->_rowCount = $count;
    }
}

2010年4月6日火曜日

PDO_ODBC + SQL Server でInsertしたシーケンスIDが取得できない

PDO_ODBCには「PDO::lastInsertId」というものが用意されているが、ドライバーが対応していないというエラーで利用する事ができなかった。。。
http://www.php.net/manual/ja/pdo.lastinsertid.php

そこでZend_Frameworkの中でZend_Db_Adpter_Pdo_Mssqlで記述されていた、

public function lastInsertId($tableName = null, $primaryKey = null)
{
    $sql = 'SELECT SCOPE_IDENTITY()';
    return (int)$this->fetchOne($sql);
}

を試してみた。

しかし、取得されるのは常に0。。。
なぜ。。。

どうしようもないし、Zend_Db_Adpter_Pdo_Mssqlだとうまく取得できるのでそこだけ一先ず、Zend_Db_Adpter_Pdo_Mssqlを利用するようにした。。。

2010年4月5日月曜日

SQL Server 用に Zend_Db_Adapter_Pdo_Odbc を作ってみた

Zend_Db_Adapter_Pdo_Mssql には不具合が多いので、結局ODBCで対応するようにした。。。
てか Zend_Db_Adapter_Pdo_Odbc が無いので作った。。。

ちなみに今回の問題は4086バイト(?)以上の値を1カラムに保存してSELECTして取得しようとすると4096バイト辺りまでしか取得できないという最悪な不具合。。。

さすがにこれはモジュールの問題なようだったので、諦めた。。。
最初からODBCでやればよかった。。。

下記がソース。

/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Db
 * @subpackage Adapter
 * @copyright  Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 *
 */


/**
 * Zend_Db_Adapter_Pdo_Abstract
 */
require_once 'Zend/Db/Adapter/Pdo/Abstract.php';


/**
 * Class for connecting to ODBC System databases and performing common operations.
 *
 * @category   Zend
 * @package    Zend_Db
 * @subpackage Adapter
 * @copyright  Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_Db_Adapter_Pdo_Odbc extends Zend_Db_Adapter_Pdo_Abstract
{
    /**
     * PDO type.
     *
     * @var string
     */
    protected $_pdoType = 'odbc';

    protected $_rowCount;

    /**
     * Keys are UPPERCASE SQL datatypes or the constants
     * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
     *
     * Values are:
     * 0 = 32-bit integer
     * 1 = 64-bit integer
     * 2 = float or decimal
     *
     * @var array Associative array of datatypes to values 0, 1, or 2.
     */
    protected $_numericDataTypes = array(
        Zend_Db::INT_TYPE    => Zend_Db::INT_TYPE,
        Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
        Zend_Db::FLOAT_TYPE  => Zend_Db::FLOAT_TYPE,
        'INT'                => Zend_Db::INT_TYPE,
        'SMALLINT'           => Zend_Db::INT_TYPE,
        'TINYINT'            => Zend_Db::INT_TYPE,
        'BIGINT'             => Zend_Db::BIGINT_TYPE,
        'DECIMAL'            => Zend_Db::FLOAT_TYPE,
        'FLOAT'              => Zend_Db::FLOAT_TYPE,
        'MONEY'              => Zend_Db::FLOAT_TYPE,
        'NUMERIC'            => Zend_Db::FLOAT_TYPE,
        'REAL'               => Zend_Db::FLOAT_TYPE,
        'SMALLMONEY'         => Zend_Db::FLOAT_TYPE
    );

    /**
     * Creates a PDO DSN for the adapter from $this->_config settings.
     *
     * @return string
     */
    protected function _dsn()
    {
        // baseline of DSN parts
        $dsn = $this->_config;

        // don't pass the username and password in the DSN
        unset($dsn['username']);
        unset($dsn['password']);
        unset($dsn['driver_options']);
        unset($dsn['port']);

        // this driver supports multiple DSN prefixes
        // @see http://www.php.net/manual/en/ref.pdo-dblib.connection.php
        if (isset($dsn['pdoType'])) {
            switch (strtolower($dsn['pdoType'])) {
                default:
                    $this->_pdoType = 'odbc';
                    break;
            }
            unset($dsn['pdoType']);
        }

        if (isset($dsn['dbname'])) {
            $dsn = $this->_pdoType . ':' . $dsn['dbname'];
        } else {
            // use all remaining parts in the DSN
            foreach ($dsn as $key => $val) {
                $dsn[$key] = "$key=$val";
            }

            $dsn = $this->_pdoType . ':' . implode(';', $dsn);
        }
        return $dsn;
    }

    /**
     * @return void
     */
    protected function _connect()
    {
        if ($this->_connection) {
            return;
        }
        parent::_connect();
        $this->_connection->exec('SET QUOTED_IDENTIFIER ON');
    }

    /**
     * Begin a transaction.
     *
     * It is necessary to override the abstract PDO transaction functions here, as
     * the PDO driver for MSSQL does not support transactions.
     */
    protected function _beginTransaction()
    {
        $this->_connect();
        $this->_connection->exec('BEGIN TRANSACTION');
        return true;
    }

    /**
     * Commit a transaction.
     *
     * It is necessary to override the abstract PDO transaction functions here, as
     * the PDO driver for MSSQL does not support transactions.
     */
    protected function _commit()
    {
        $this->_connect();
        $this->_connection->exec('COMMIT TRANSACTION');
        return true;
    }

    /**
     * Roll-back a transaction.
     *
     * It is necessary to override the abstract PDO transaction functions here, as
     * the PDO driver for MSSQL does not support transactions.
     */
    protected function _rollBack() {
        $this->_connect();
        $this->_connection->exec('ROLLBACK TRANSACTION');
        return true;
    }

    /**
     * Returns a list of the tables in the database.
     *
     * @return array
     */
    public function listTables()
    {
        $sql = "SELECT name FROM sysobjects WHERE type = 'U' ORDER BY name";
        return $this->fetchCol($sql);
    }

    /**
     * Returns the column descriptions for a table.
     *
     * The return value is an associative array keyed by the column name,
     * as returned by the RDBMS.
     *
     * The value of each array element is an associative array
     * with the following keys:
     *
     * SCHEMA_NAME      => string; name of database or schema
     * TABLE_NAME       => string;
     * COLUMN_NAME      => string; column name
     * COLUMN_POSITION  => number; ordinal position of column in table
     * DATA_TYPE        => string; SQL datatype name of column
     * DEFAULT          => string; default expression of column, null if none
     * NULLABLE         => boolean; true if column can have nulls
     * LENGTH           => number; length of CHAR/VARCHAR
     * SCALE            => number; scale of NUMERIC/DECIMAL
     * PRECISION        => number; precision of NUMERIC/DECIMAL
     * UNSIGNED         => boolean; unsigned property of an integer type
     * PRIMARY          => boolean; true if column is part of the primary key
     * PRIMARY_POSITION => integer; position of column in primary key
     * PRIMARY_AUTO     => integer; position of auto-generated column in primary key
     *
     * @todo Discover column primary key position.
     * @todo Discover integer unsigned property.
     *
     * @param string $tableName
     * @param string $schemaName OPTIONAL
     * @return array
     */
    public function describeTable($tableName, $schemaName = null)
    {
        if ($schemaName != null) {
            if (strpos($schemaName, '.') !== false) {
                $result = explode('.', $schemaName);
                $schemaName = $result[1];
            }
        }
        /**
         * Discover metadata information about this table.
         */
        $sql = "exec sp_columns @table_name = " . $this->quoteIdentifier($tableName, true);
        if ($schemaName != null) {
            $sql .= ", @table_owner = " . $this->quoteIdentifier($schemaName, true);
        }

        $stmt = $this->query($sql);
        $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
        $stmt->closeCursor();

        $table_name  = 2;
        $column_name = 3;
        $type_name   = 5;
        $precision   = 6;
        $length      = 7;
        $scale       = 8;
        $nullable    = 10;
        $column_def  = 12;
        $column_position = 16;

        /**
         * Discover primary key column(s) for this table.
         */
        $sql = "exec sp_pkeys @table_name = " . $this->quoteIdentifier($tableName, true);
        if ($schemaName != null) {
            $sql .= ", @table_owner = " . $this->quoteIdentifier($schemaName, true);
        }

        $stmt = $this->query($sql);
        $primaryKeysResult = $stmt->fetchAll(Zend_Db::FETCH_NUM);
        $stmt->closeCursor();
        $pkey_column_name = 3;
        $pkey_key_seq = 4;
        foreach ($primaryKeysResult as $pkeysRow) {
            $primaryKeyColumn[$pkeysRow[$pkey_column_name]] = $pkeysRow[$pkey_key_seq];
        }

        $desc = array();
        $p = 1;
        foreach ($result as $key => $row) {
            $identity = false;
            $words = explode(' ', $row[$type_name], 2);
            if (isset($words[0])) {
                $type = $words[0];
                if (isset($words[1])) {
                    $identity = (bool) preg_match('/identity/', $words[1]);
                }
            }

            $isPrimary = array_key_exists($row[$column_name], $primaryKeyColumn);
            if ($isPrimary) {
                $primaryPosition = $primaryKeyColumn[$row[$column_name]];
            } else {
                $primaryPosition = null;
            }

            $desc[$this->foldCase($row[$column_name])] = array(
                'SCHEMA_NAME'      => null, // @todo
                'TABLE_NAME'       => $this->foldCase($row[$table_name]),
                'COLUMN_NAME'      => $this->foldCase($row[$column_name]),
                'COLUMN_POSITION'  => (int) $row[$column_position],
                'DATA_TYPE'        => $type,
                'DEFAULT'          => $row[$column_def],
                'NULLABLE'         => (bool) $row[$nullable],
                'LENGTH'           => $row[$length],
                'SCALE'            => $row[$scale],
                'PRECISION'        => $row[$precision],
                'UNSIGNED'         => null, // @todo
                'PRIMARY'          => $isPrimary,
                'PRIMARY_POSITION' => $primaryPosition,
                'IDENTITY'         => $identity
            );
        }
        return $desc;
    }

    /**
     * Adds an adapter-specific LIMIT clause to the SELECT statement.
     *
     * @link http://lists.bestpractical.com/pipermail/rt-devel/2005-June/007339.html
     *
     * @param string $sql
     * @param integer $count
     * @param integer $offset OPTIONAL
     * @return string
     */
     public function limit($sql, $count, $offset = 0)
     {

        $count = intval($count);
        if ($count <= 0) {
            /** @see Zend_Db_Adapter_Exception */
            require_once 'Zend/Db/Adapter/Exception.php';
            throw new Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid");
        }

        $offset = intval($offset);
        if ($offset < 0) {
            /** @see Zend_Db_Adapter_Exception */
            require_once 'Zend/Db/Adapter/Exception.php';
            throw new Zend_Db_Adapter_Exception("LIMIT argument offset=$offset is not valid");
        }

        $sql = preg_replace(
            '/^SELECT\s+(DISTINCT\s)?/i',
            'SELECT $1TOP ' . ($count+$offset) . ' ',
            $sql
            );

        if ($offset > 0) {
            $orderby = stristr($sql, 'ORDER BY');

            if ($orderby !== false) {
                $orderParts = explode(',', substr($orderby, 8));
                $pregReplaceCount = null;
                $orderbyInverseParts = array();
                foreach ($orderParts as $orderPart) {
                    $orderPart = rtrim($orderPart);
                    $inv = preg_replace('/\s+desc$/i', ' ASC', $orderPart, 1, $pregReplaceCount);
                    if ($pregReplaceCount) {
                        $orderbyInverseParts[] = $inv;
                        continue;
                    }
                    $inv = preg_replace('/\s+asc$/i', ' DESC', $orderPart, 1, $pregReplaceCount);
                    if ($pregReplaceCount) {
                        $orderbyInverseParts[] = $inv;
                        continue;
                    } else {
                        $orderbyInverseParts[] = $orderPart . ' DESC';
                    }
                }

                $orderbyInverse = 'ORDER BY ' . implode(', ', $orderbyInverseParts);
            }

            if (!is_null($this->getRowCount()) && ($count+$offset) > $this->getRowCount())
                $count = $this->getRowCount() - $offset;

            $sql = 'SELECT * FROM (SELECT TOP ' . $count . ' * FROM (' . $sql . ') AS inner_tbl';
            if ($orderby !== false) {
                $sql .= ' ' . $orderbyInverse . ' ';
            }
            $sql .= ') AS outer_tbl';
            if ($orderby !== false) {
                $sql .= ' ' . $orderby;
            }
        }

        return $sql;
    }

    /**
     * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
     *
     * As a convention, on RDBMS brands that support sequences
     * (e.g. Oracle, PostgreSQL, DB2), this method forms the name of a sequence
     * from the arguments and returns the last id generated by that sequence.
     * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
     * returns the last value generated for such a column, and the table name
     * argument is disregarded.
     *
     * Microsoft SQL Server does not support sequences, so the arguments to
     * this method are ignored.
     *
     * @param string $tableName   OPTIONAL Name of table.
     * @param string $primaryKey  OPTIONAL Name of primary key column.
     * @return string
     * @throws Zend_Db_Adapter_Exception
     */
    public function lastInsertId($tableName = null, $primaryKey = null)
    {
        $sql = 'SELECT SCOPE_IDENTITY()';
        return (int)$this->fetchOne($sql);
    }

    public function getServerVersion()
    {
        try {
            $stmt = $this->query("SELECT SERVERPROPERTY('productversion')");
            $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
            if (count($result)) {
                return $result[0][0];
            }
            return null;
        } catch (PDOException $e) {
            return null;
        }
    }

    public function quote($value)
    {
        if (is_int($value) || is_float($value)) {
            return $value;
        }
        else if (is_array($value)) {
            $values = array();
            foreach ($value as $_val) {
                if (is_int($_val) || is_float($_val)) {
                    $values[] = $_val;
                }
                else {
                    $values[] = "'".addslashes($_val)."'";
                }
            }
            return join(',', $values);
        }

        return "'".addslashes($value)."'";
    }

    public function getRowCount()
    {
        return $this->_rowCount;
    }

    public function setRowCount($count)
    {
        $this->_rowCount = $count;
    }
}

Followers