動作はしているが手をつけていなかったPostgreSQLにテーブルを追加してハマった。
結論
Laravel側でPostgreSQLのRETURNING句を使用しようとするが、RETURNING句は8.2から実装されているため8.1では動作しない。
Laravelを修正し、手動インクリメントを行う。
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は新しいものを使いましょう。 使わせて下さい。
経緯
新規テーブルを作成する
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
調査
/**
* 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));
を調査
/**
* 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に主キーを含めればいいと判明
/**
* 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していた。
表面上はなんの問題も無く動いているため原因がまったく予想外の箇所だった。