LoginSignup
3
3

More than 5 years have passed since last update.

Laravel3 PostgreSQL8.1以下を使う

Last updated at Posted at 2015-02-27

動作はしているが手をつけていなかったPostgreSQLにテーブルを追加してハマった。

結論

Laravel側でPostgreSQLのRETURNING句を使用しようとするが、RETURNING句は8.2から実装されているため8.1では動作しない。

Laravelを修正し、手動インクリメントを行う。

/framework/laravel/database/query/grammars/postgres.php

    public function insert_get_id(Query $query, $values, $column)
    {
        // return $this->insert($query, $values)." RETURNING $column";
        // Version 8.1では、RETURNINGがないのでコメントアウト
        return $this->insert($query, $values);
    }

手動インクリメントによるインサート

Tables::create(array(
    `id` => Tables::max('id') + 1;
    'h'  => 1,
    'o'  => 2,
    'g'  => 3,
    'e'  => 4,
))

フレームワークやデータベースやPHPは新しいものを使いましょう。 使わせて下さい。

経緯

新規テーブルを作成する

migrate
   public function up()
    {
        Config::set('database.default', 'pgsql');
        Schema::create('tables', function ($table) {
            $table->increments('id');
            $table->integer('h')->nullable();
            $table->integer('o')->nullable();
            $table->integer('g')->nullable();
            $table->integer('e')->nullable();
            $table->timestamps();
        });
    }

テーブルにインサートする

Table::create(array(
    'h' => 1,
    'o' => 2,
    'g' => 3,
    'e' => 4,
))

IDはオートインクリメントなので指定しない。timestampも自動で登録してくるはずだが…

2015-02-25 18:52:07 ERROR - Undefined index: id in /framework/laravel/database/query.php on line 834

調査

/framework/laravel/database/query.php

    /**
     * Insert an array of values into the database table and return the key.
     *
     * @param  array   $values
     * @param  string  $column
     * @return mixed
     */
    public function insert_get_id($values, $column = 'id')
    {
        $sql = $this->grammar->insert_get_id($this, $values, $column);

        $result = $this->connection->query($sql, array_values($values));

        // If the key is not auto-incrementing, we will just return the inserted value
        if (isset($values[$column]))
        {
            return $values[$column];
        }
        else if ($this->grammar instanceof Postgres)
        {
            $row = (array) $result[0];
            //↓ 834行目
            return (int) $row[$column];
        }
        else
        {
            return (int) $this->connection->pdo->lastInsertId();
        }
    }

$row['id']が存在しない模様。

$result = $this->connection->query($sql, array_values($values));

を調査

/framework/laravel/database/connection.php
    /**
     * Execute a SQL query and return an array of StdClass objects.
     *
     * @param  string  $sql
     * @param  array   $bindings
     * @return array
     */
    public function query($sql, $bindings = array())
    {
        $sql = trim($sql);

        list($statement, $result) = $this->execute($sql, $bindings);

        // The result we return depends on the type of query executed against the
        // database. On SELECT clauses, we will return the result set, for update
        // and deletes we will return the affected row count.
        if (stripos($sql, 'select') === 0 || stripos($sql, 'show') === 0)
        {
            return $this->fetch($statement, Config::get('database.fetch'));
        }
        elseif (stripos($sql, 'update') === 0 or stripos($sql, 'delete') === 0)
        {
            return $statement->rowCount();
        }
        // For insert statements that use the "returning" clause, which is allowed
        // by database systems such as Postgres, we need to actually return the
        // real query result so the consumer can get the ID.
        elseif (stripos($sql, 'insert') === 0 and stripos($sql, 'returning') !== false)
        {
            return $this->fetch($statement, Config::get('database.fetch'));
        }
        else
        {
            return $result;
        }
    }

デバッグすると、

return $result;

を通っているのか?
insert_get_id()ではIDが得られることを期待しているのに、$resultには結果のboolしか入っていない。
そもそも

        
        elseif (stripos($sql, 'insert') === 0 and stripos($sql, 'returning') !== false)
        {
            return $this->fetch($statement, Config::get('database.fetch'));
        }

ここを通過するべきだろうに。
で、原因を探したら他の人が丁寧に'returning'を取り除いてくれていた。(結論参照)

returningを使うにはPostgreSQLをバージョンアップする必要があるが、無理。どうにか出来ないかと眺め、SQLに主キーを含めればいいと判明

再掲/framework/laravel/database/query.php

    /**
     * Insert an array of values into the database table and return the key.
     *
     * @param  array   $values
     * @param  string  $column
     * @return mixed
     */
    public function insert_get_id($values, $column = 'id')
    {
        $sql = $this->grammar->insert_get_id($this, $values, $column);

        $result = $this->connection->query($sql, array_values($values));

        // If the key is not auto-incrementing, we will just return the inserted value
        if (isset($values[$column]))
        {  //↓ ここで返せばいい。↑$valuesにidを渡そう
            return $values[$column];
        }
        else if ($this->grammar instanceof Postgres)
        {
            $row = (array) $result[0];
            //↓ 834行目 ここがだめなら
            return (int) $row[$column];
        }
        else
        {
            return (int) $this->connection->pdo->lastInsertId();
        }
    }

根本的な解決ではなく、オートインクリメントが永久的に使用できないが、回避だけはできた…と思う。

余談

今までエラーが出ていなかったのはPostgreSQL側では値のUPDATEかMySQLからの同期でのINSERT(IDもMySQL側の値を指定)しかしていなかったのでエラーが発生しなかった。
UPDATEする値のmigrate時にはスキーマビルダーでテーブル作成後に生SQLでINSERTしていた。
表面上はなんの問題も無く動いているため原因がまったく予想外の箇所だった。

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3