Firebird DBMSサポートをLaravelフレームワークに追加する

Firebird DBMSを使用したPHP Webアプリケーションの例(後でリンクが作成されます)を作成するとき、MVCアーキテクチャモデルを使用して開発用のフレームワークを選択するという疑問が生じました。 PHPのフレームワークの選択は非常に大きいですが、最も便利で、シンプルで、簡単に拡張できるのはLaravelのようです。 ただし、このフレームワークはすぐに使用できるDBMS Firebirdをサポートしていません。 LaravelはPDOドライバーを使用してデータベースを操作します。 Firebird用のPDOドライバーがあるので、LaravelをFirebirdである程度の努力で動作させることが可能だと思いました。



Laravelは、MVCアーキテクチャモデル(Eng。Model View Controller-model-view-controller)を使用した開発用に設計された無料のオープンソースWebフレームワークです。 Laravelは、Webアプリケーションを構築するための便利で簡単に拡張可能なフレームワークです。 Laravelフレームワークは、デフォルトで4つのDBMS、MySQL、Postgres、SQLite、MS SQL Serverをサポートしています。 この記事では、別のFirebird DBMSを追加する方法を説明します。



FirebirdConnection接続クラス



Illuminate \ Database \ Connectors \ ConnectionFactoryファクトリを使用してデータベースに接続するたびに、Illuminate \ Database \ ConnectionInterfaceインターフェイスを実装するDBMSのタイプに応じて特定の接続インスタンスが作成されます。 さらに、ファクトリーはコネクターを作成します。コネクターは、構成パラメーターに基づいて接続文字列を形成し、PDO接続デザイナーに渡します。 コネクタはcreateConnectorメソッドで作成されます。 Firebirdのコネクタを作成できるように、少し変更します。



public function createConnector(array $config) { if (! isset($config['driver'])) { throw new InvalidArgumentException('A driver must be specified.'); } if ($this->container->bound($key = "db.connector.{$config['driver']}")) { return $this->container->make($key); } switch ($config['driver']) { case 'mysql': return new MySqlConnector; case 'pgsql': return new PostgresConnector; case 'sqlite': return new SQLiteConnector; case 'sqlsrv': return new SqlServerConnector; case 'firebird': // Add support Firebird return new FirebirdConnector; } throw new InvalidArgumentException("Unsupported driver [{$config['driver']}]"); }
      
      





Illuminate \ Database \ Connectors \ FirebirdConnectorコネクタクラス自体は、後ほど定義します。 それまでの間、Illuminate \ Database \ ConnectionInterfaceインターフェイスを実装する接続を作成するように設計されたcreateConnectionメソッドを変更します。



 protected function createConnection($driver, $connection, $database, $prefix = '', array $config = []) { if ($this->container->bound($key = "db.connection.{$driver}")) { return $this->container->make($key, [$connection, $database, $prefix, $config]); } switch ($driver) { case 'mysql': return new MySqlConnection($connection, $database, $prefix, $config); case 'pgsql': return new PostgresConnection($connection, $database, $prefix, $config); case 'sqlite': return new SQLiteConnection($connection, $database, $prefix, $config); case 'sqlsrv': return new SqlServerConnection($connection, $database, $prefix, $config); case 'firebird': // Add support Firebird return new FirebirdConnection($connection, $database, $prefix, $config); } throw new InvalidArgumentException("Unsupported driver [$driver]"); }
      
      





次に、Firebirdのコネクタの作成に移りましょう-Illuminate \ Database \ Connectors \ FirebirdConnectorクラス。 Postgres用のコネクタなど、既存のコネクタをベースとして使用し、Firebird用にリメイクできます。



まず、 ドキュメントに記載されている文字列の形式に従って、接続文字列を形成する方法を変更します



 protected function getDsn(array $config) { $dsn = "firebird:dbname="; if (isset($config['host'])) { $dsn .= $config['host']; } if (isset($config['port'])) { $dsn .= "/" . $config['port']; } $dsn .= ":" . $config['database']; if (isset($config['charset'])) { $dsn .= ";charset=" . $config['charset']; } if (isset($config['role'])) { $dsn .= ";role=" . $config['role']; } return $dsn; }
      
      





この場合、PDO接続を作成するメソッドは簡素化されます。



 public function connect(array $config) { $dsn = $this->getDsn($config); $options = $this->getOptions($config); // We need to grab the PDO options that should be used while making the brand // new connection instance. The PDO options control various aspects of the // connection's behavior, and some might be specified by the developers. $connection = $this->createConnection($dsn, $config, $options); return $connection; }
      
      





Laravelで使用されるさまざまなDBMSの接続自体は、Illuminate \ Database \ Connectionクラスを継承します。 Laravel、特にORM Eloquentで使用されるデータベースを操作するためのすべての可能性をそれ自体でカプセル化するのは、このクラスです。 後続のクラス(DBMSの各タイプ)には、DMLクエリを構築するときに必要な文法記述と、DDLクエリ(移行で使用)の文法インスタンスを持つクラスのインスタンスを返すメソッドが実装され、スキーマ名をテーブルに追加します。 このクラスは次のようになります。



 namespace Illuminate\Database; use Illuminate\Database\Query\Processors\FirebirdProcessor; use Doctrine\DBAL\Driver\PDOFirebird\Driver as DoctrineDriver; use Illuminate\Database\Query\Grammars\FirebirdGrammar as QueryGrammar; use Illuminate\Database\Schema\Grammars\FirebirdGrammar as SchemaGrammar; class FirebirdConnection extends Connection { /** * Get the default query grammar instance. * * @return \Illuminate\Database\Query\Grammars\FirebirdGrammar */ protected function getDefaultQueryGrammar() { return $this->withTablePrefix(new QueryGrammar); } /** * Get the default schema grammar instance. * * @return \Illuminate\Database\Schema\Grammars\FirebirdGrammar */ protected function getDefaultSchemaGrammar() { return $this->withTablePrefix(new SchemaGrammar); } /** * Get the default post processor instance. * * @return \Illuminate\Database\Query\Processors\FirebirdProcessor */ protected function getDefaultPostProcessor() { return new FirebirdProcessor; } /** * Get the Doctrine DBAL driver. * * @return \Doctrine\DBAL\Driver\PDOFirebird\Driver */ protected function getDoctrineDriver() { return new DoctrineDriver; } }
      
      





Doctrineドライバーのインスタンスを返すメソッドは正式に必要であるため、紹介しますが、Doctrineを使用する(Eloquentのみで)という目標はなかったため、実装しませんでした。 ご希望の場合は、自分で行うことができます。



Illuminate \ Database \ Query \ Processors \ FirebirdProcessorポストプロセッサは、クエリ結果の追加処理を目的としています。特に、INSERTリクエストからレコード識別子を抽出するのに役立ちます。 その実装の定義は、Illuminate \ Database \ Query \ Processors \ PostgresProcessorから完全にコピーされます。



DMLクエリを構築するための文法クラス



次に、最も興味深く重要なもの、つまりDMLクエリを構築するための文法の説明に目を向けます。 Laravelクエリビルダ用に記述された式を、DBMSで使用されるSQL言語の方言に変換するのは、このクラスです。 特定の構成が1つのDBMSで何をするかを知っていれば、Firebird用にこの構成を簡単に作成できます。 実際、SQL言語のDML部分はかなり標準的なものであり、少なくともLaravelを使用して構築できるクエリに関しては、さまざまなDBMSに対して大きな違いはありません。



DMLクエリ文法は、Illuminate \ Database \ Query \ Grammars \ Grammarクラスを継承します。 保護された$ selectComponentsプロパティは、クエリが\ Illuminate \ Database \ Query \ Builderビルダーによって組み立てられるSELECTクエリの部分をリストします。 compileComponentsメソッドでは、これらの部分はバイパスされ、それぞれについて、要求部分の名前とコンパイルプレフィックスで構成される名前でメソッドが呼び出されます。



  /** * Compile the components necessary for a select clause. * * @param \Illuminate\Database\Query\Builder $query * @return array */ protected function compileComponents(Builder $query) { $sql = []; foreach ($this->selectComponents as $component) { // To compile the query, we'll spin through each component of the query and // see if that component exists. If it does we'll just call the compiler // function for the component which is responsible for making the SQL. if (! is_null($query->$component)) { $method = 'compile'.ucfirst($component); $sql[$component] = $this->$method($query, $query->$component); } } return $sql; }
      
      





この事実を知って、何をどこで修正するかが明らかになります。 次に、Illuminate \ Database \ Query \ Grammars \ Grammarクラスの子孫を作成して、Firebirdの文法を定義します-Illuminate \ Database \ Query \ Grammars \ FirebirdGrammar。 Firebirdの主な特徴を定義します。



単純なINSERTクエリの主な違いは、RETURNING句を使用して追加したばかりの行を返す機能です。 Laravelでは、これは追加したばかりの文字列の識別子を返すために使用されます。 ただし、MySQLにはこの機能がないため、compileInsertGetIdメソッドはDBMSごとに異なって見えます。 Firebirdは、Postgres DBMSと同様にRETURNING句をサポートしているため、このメソッドはPostgresの文法から取得できます。 次のようになります。



  /** * Compile an insert and get ID statement into SQL. * * @param \Illuminate\Database\Query\Builder $query * @param array $values * @param string $sequence * @return string */ public function compileInsertGetId(Builder $query, $values, $sequence) { if (is_null($sequence)) { $sequence = 'id'; } return $this->compileInsert($query, $values) . ' returning ' . $this->wrap($sequence); }
      
      





おそらく、SELECTクエリの最も重要な違いは、クエリによって返されるレコードの数の制限です。これは、ページナビゲーションでよく使用されます。 たとえば、次の式:



 DB::table('goods')->orderBy('name')->skip(10)->take(20)->get();
      
      





さまざまなDBMSで、SQLの見方が大きく異なります。 MySQLでは、次のようになります。



 SELECT * FROM goods ORDER BY name LIMIT 10, 20
      
      





このようなPostgresでは:



 SELECT * FROM goods ORDER BY name LIMIT 20 OFFSET 10
      
      





Firebirdには3つのオプションがあります。 バージョン1.5以降:



 SELECT FIRST(10) SKIP(20) * FROM goods ORDER BY name
      
      





バージョン2.0以降、別の構造が追加されます。

 SELECT * FROM goods ORDER BY name ROWS 21, 30
      
      





バージョン3.0以降、SQL-2011標準の構造が追加されました。



 SELECT * FROM color ORDER BY name OFFSET 20 ROWS FETCH FIRST 10 ROWS ONLY
      
      





もちろん、最も便利で正しいオプションは標準のものですが、LaravelでFirebird 2.5と3.0の両方をサポートしたかったので、2番目のオプションを選択します。 この場合、compileLimitメソッドとcompileOffsetメソッドは次のようになります。



  /** * Compile the "limit" portions of the query. * * @param \Illuminate\Database\Query\Builder $query * @param int $limit * @return string */ protected function compileLimit(Builder $query, $limit) { if ($query->offset) { $first = (int) $query->offset + 1; return 'rows ' . (int) $first; } else { return 'rows ' . (int) $limit; } } /** * Compile the "offset" portions of the query. * * @param \Illuminate\Database\Query\Builder $query * @param int $offset * @return string */ protected function compileOffset(Builder $query, $offset) { if ($query->limit) { if ($offset) { $end = (int) $query->limit + (int) $offset; return 'to ' . $end; } else { return ''; } } else { $begin = (int) $offset + 1; return 'rows ' . $begin . ' to 2147483647'; } }
      
      





クエリを区別する次のことは、日付の一部を抽出することです。これは、dateBasedWhereメソッドを使用して行われます。 Firebirdはこれに標準のEXTRACT関数を使用します。 これを念頭に置いて、メソッドは次のようになります。



  /** * Compile a date based where clause. * * @param string $type * @param \Illuminate\Database\Query\Builder $query * @param array $where * @return string */ protected function dateBasedWhere($type, Builder $query, $where) { $value = $this->parameter($where['value']); return 'extract(' . $type . ' from ' . $this->wrap($where['column']) . ') ' . $where['operator'] . ' ' . $value; }
      
      





それだけです。すべての主要な特徴的な機能が考慮されています。 完全に実装されたクラスIlluminate \ Database \ Query \ Grammars \ FirebirdGrammarは、記事に添付されているソースコードにあります。



DDLクエリを構築するための文法クラス



次に、データベーススキーマの構築に使用される、より複雑な文法に目を向けます。 この文法は、いわゆる移行(Laravelの観点から)に使用されます。 データ型から自動インクリメントフィールドまで、さまざまなDBMSにはさらに多くの違いがあります。 さらに、ここでは、テーブルまたは列の存在を判断するために、ここでいくつかのシステムテーブルに対するクエリを書き換える必要があります。



文法DDLクエリは、クラスIlluminate \ Database \ Schema \ Grammars \ Grammarを継承します。 独自の文法Illuminate \データベース\スキーマ\文法\ FirebirdGrammarを作成します。 保護されたプロパティ$修飾子は、テーブルフィールド修飾子をリストします。配列にリストされた修飾子ごとに、modifyで始まり、修飾子の名前が続くメソッドが必要です。 これらのメソッドは、MySQLの文法と類似していますが、Firebirdの仕様を考慮に入れて作成しています。



列修飾子のサポート
 class FirebirdGrammar extends Grammar { /** * The possible column modifiers. * * @var array */ protected $modifiers = ['Charset', 'Collate', 'Increment', 'Nullable', 'Default']; /** * The columns available as serials. * * @var array */ protected $serials = ['bigInteger', 'integer', 'mediumInteger', 'smallInteger', 'tinyInteger']; // ……………………  ……………… /** * Get the SQL for a character set column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyCharset(Blueprint $blueprint, Fluent $column) { if (! is_null($column->charset)) { return ' character set '.$column->charset; } } /** * Get the SQL for a collation column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyCollate(Blueprint $blueprint, Fluent $column) { if (! is_null($column->collation)) { return ' collate '.$column->collation; } } /** * Get the SQL for a nullable column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyNullable(Blueprint $blueprint, Fluent $column) { return $column->nullable ? '' : ' not null'; } /** * Get the SQL for a default column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyDefault(Blueprint $blueprint, Fluent $column) { if (!is_null($column->default)) { return ' default ' . $this->getDefaultValue($column->default); } } /** * Get the SQL for an auto-increment column modifier. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $column * @return string|null */ protected function modifyIncrement(Blueprint $blueprint, Fluent $column) { if (in_array($column->type, $this->serials) && $column->autoIncrement) { return ' primary key'; } } // ……………………  ……………… }
      
      







$ serials配列は、Increment修飾子(自動インクリメント列)が使用可能なタイプ(Laravelで使用可能)をリストします。 Laravelで利用可能なタイプは個別に検討する必要があります。 Laravelで利用可能なタイプは、移行ドキュメントの「利用可能な列タイプ」にリストされています。 Laravelで使用可能なタイプを文法内の特定のDBMSのデータタイプに変換するには、typeという単語で始まり、その後にタイプ名が続くメソッドが使用されます。



データ型のサポート
  /** * Create the column definition for a char type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeChar(Fluent $column) { return "char({$column->length})"; } /** * Create the column definition for a string type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeString(Fluent $column) { return "varchar({$column->length})"; } /** * Create the column definition for a text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeText(Fluent $column) { return 'BLOB SUB_TYPE TEXT'; } /** * Create the column definition for a medium text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMediumText(Fluent $column) { return 'BLOB SUB_TYPE TEXT'; } /** * Create the column definition for a long text type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeLongText(Fluent $column) { return 'BLOB SUB_TYPE TEXT'; } /** * Create the column definition for a integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeInteger(Fluent $column) { return $column->autoIncrement ? 'INTEGER GENERATED BY DEFAULT AS IDENTITY' : 'INTEGER'; } /** * Create the column definition for a big integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeBigInteger(Fluent $column) { return $column->autoIncrement ? 'BIGINT GENERATED BY DEFAULT AS IDENTITY' : 'BIGINT'; } /** * Create the column definition for a medium integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMediumInteger(Fluent $column) { return $column->autoIncrement ? 'INTEGER GENERATED BY DEFAULT AS IDENTITY' : 'INTEGER'; } /** * Create the column definition for a tiny integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTinyInteger(Fluent $column) { return $column->autoIncrement ? 'SMALLINT GENERATED BY DEFAULT AS IDENTITY' : 'SMALLINT'; } /** * Create the column definition for a small integer type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeSmallInteger(Fluent $column) { return $column->autoIncrement ? 'SMALLINT GENERATED BY DEFAULT AS IDENTITY' : 'SMALLINT'; } /** * Create the column definition for a float type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeFloat(Fluent $column) { return $this->typeDouble($column); } /** * Create the column definition for a double type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDouble(Fluent $column) { return 'double precision'; } /** * Create the column definition for a decimal type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDecimal(Fluent $column) { return "decimal({$column->total}, {$column->places})"; } /** * Create the column definition for a boolean type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeBoolean(Fluent $column) { return 'boolean'; } /** * Create the column definition for an enum type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeEnum(Fluent $column) { $allowed = array_map(function ($a) { return "'" . $a . "'"; }, $column->allowed); return "varchar(255) check (\"{$column->name}\" in (" . implode(', ', $allowed) . '))'; } /** * Create the column definition for a json type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeJson(Fluent $column) { return 'varchar(8191)'; } /** * Create the column definition for a jsonb type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeJsonb(Fluent $column) { return 'varchar(8191)'; } /** * Create the column definition for a date type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDate(Fluent $column) { return 'date'; } /** * Create the column definition for a date-time type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDateTime(Fluent $column) { return 'timestamp'; } /** * Create the column definition for a date-time type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeDateTimeTz(Fluent $column) { return 'timestamp'; } /** * Create the column definition for a time type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTime(Fluent $column) { return 'time'; } /** * Create the column definition for a time type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTimeTz(Fluent $column) { return 'time'; } /** * Create the column definition for a timestamp type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTimestamp(Fluent $column) { if ($column->useCurrent) { return 'timestamp default CURRENT_TIMESTAMP'; } return 'timestamp'; } /** * Create the column definition for a timestamp type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeTimestampTz(Fluent $column) { if ($column->useCurrent) { return 'timestamp default CURRENT_TIMESTAMP'; } return 'timestamp'; } /** * Create the column definition for a binary type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeBinary(Fluent $column) { return 'varchar(8191) CHARACTER SET OCTETS'; } /** * Create the column definition for a uuid type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeUuid(Fluent $column) { return 'char(36)'; } /** * Create the column definition for an IP address type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeIpAddress(Fluent $column) { return 'varchar(45)'; } /** * Create the column definition for a MAC address type. * * @param \Illuminate\Support\Fluent $column * @return string */ protected function typeMacAddress(Fluent $column) { return 'varchar(17)'; }
      
      





自動インクリメント列に関する注意



ID列は、Firebird 3.0以降で使用できます。 バージョン3.0より前では、ジェネレーターとBEFORE INSERTトリガーが同様の機能に使用されていました。 例:



 CREATE TABLE USERS ( ID INTEGER GENERATED BY DEFAULT AS IDENTITY, … );
      
      





同様の機能は次のように取得できます。



 CREATE TABLE USERS ( ID INTEGER, … ); CREATE SEQUENCE SEQ_USERS; CREATE TRIGGER TR_USERS_BI FOR USERS ACTIVE BEFORE INSERT AS BEGIN IF (NEW.ID IS NULL) THEN NEW.ID = NEXT VALUE FOR SEQ_USERS; END
      
      





Laravelの移行では、テーブル以外のスキーマオブジェクトはサポートされていません。 つまり シーケンスの作成と変更、さらにトリガーはサポートされていません。 ただし、シーケンスは、PostgresやMS SQL(2012年以降)など、かなり多数のDBMSの機能の一部です。 Laravel移行にシーケンスサポートを追加する方法については、この記事の後半で説明します。



次に、クエリが返す2つのメソッドを追加して、テーブルとテーブル内の列の存在を確認します。



 /** * Compile the query to determine if a table exists. * * @return string */ public function compileTableExists() { return 'select * from RDB$RELATIONS where RDB$RELATION_NAME = ?'; } /** * Compile the query to determine the list of columns. * * @param string $table * @return string */ public function compileColumnExists($table) { return "select TRIM(RDB\$FIELD_NAME) AS \"column_name\" from RDB\$RELATION_FIELDS where RDB\$RELATION_NAME = '$table'"; }
      
      





compileCreateメソッドを追加して、CREATE TABLEステートメントを作成します。 同じ方法を使用して、一時的なGTTテーブルを作成します。 非常に奇妙ですが、Postgres DBMSの場合でも、1つのタイプのGTTのみが作成されます-ON COMMIT DELETE ROWSですが、両方のタイプのGTTのサポートを一度に実装します。



  /** * Compile a create table command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileCreate(Blueprint $blueprint, Fluent $command) { $columns = implode(', ', $this->getColumns($blueprint)); $sql = $blueprint->temporary ? 'create temporary' : 'create'; $sql .= ' table ' . $this->wrapTable($blueprint) . " ($columns)"; if ($blueprint->temporary) { if ($blueprint->preserve) { $sql .= ' ON COMMIT DELETE ROWS'; } else { $sql .= ' ON COMMIT PRESERVE ROWS'; } } return $sql; }
      
      





Illuminate \ Database \ Schema \ Blueprintクラスには$ preserveプロパティが含まれていないため、追加する方法とインストールする方法を追加しましょう。 Blueprintクラスは、クエリまたはクエリセットを生成して、テーブルメタデータの作成、変更、削除をサポートするように設計されています。



 class Blueprint { // ……………  /** * Whether a temporary table such as ON COMMIT PRESERVE ROWS * * @var bool */ public $preserve = false; // ……………  /** * Indicate that the temporary table as ON COMMIT PRESERVE ROWS. * * @return void */ public function preserveRows() { $this->preserve = true; } // ……………  }
      
      





Illuminate \ Database \ Schema \ Grammars \ FirebirdGrammar文法クラスに戻ります。 メソッドを追加して、テーブル削除演算子を作成します。



  /** * Compile a drop table command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDrop(Blueprint $blueprint, Fluent $command) { return 'drop table ' . $this->wrapTable($blueprint); }
      
      





Laravelには、テーブルが存在する場合にのみテーブルを削除しようとする別の方法があります。 これは、SQL DROP TABLE IF EXISTSステートメントを使用して行われます。 Firebirdには同様の機能を持つ演算子はありませんが、匿名ブロック(EXECUTE BLOCK + EXECUTE STATEMENT)を使用してエミュレートできます。



  /** * Compile a drop table (if exists) command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropIfExists(Blueprint $blueprint, Fluent $command) { $sql = 'EXECUTE BLOCK' . "\n"; $sql .= 'AS' . "\n"; $sql .= 'BEGIN' . "\n"; $sql .= " IF (EXISTS(select * from RDB\$RELATIONS where RDB\$RELATION_NAME = '" . $blueprint->getTable() . "')) THEN" . "\n"; $sql .= " EXECUTE STATEMENT 'DROP TABLE " . $this->wrapTable($blueprint) . "';" . "\n"; $sql .= 'END'; return $sql; }
      
      





次に、列、制約、およびインデックスを追加および削除するメソッドを追加します。



列、制約、およびインデックスを追加および削除する方法
  /** * Compile a column addition command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileAdd(Blueprint $blueprint, Fluent $command) { $table = $this->wrapTable($blueprint); $columns = $this->prefixArray('add column', $this->getColumns($blueprint)); return 'alter table ' . $table . ' ' . implode(', ', $columns); } /** * Compile a primary key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compilePrimary(Blueprint $blueprint, Fluent $command) { $columns = $this->columnize($command->columns); return 'alter table ' . $this->wrapTable($blueprint) . " add primary key ({$columns})"; } /** * Compile a unique key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileUnique(Blueprint $blueprint, Fluent $command) { $table = $this->wrapTable($blueprint); $index = $this->wrap($command->index); $columns = $this->columnize($command->columns); return "alter table $table add constraint {$index} unique ($columns)"; } /** * Compile a plain index key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileIndex(Blueprint $blueprint, Fluent $command) { $columns = $this->columnize($command->columns); $index = $this->wrap($command->index); return "create index {$index} on " . $this->wrapTable($blueprint) . " ({$columns})"; } /** * Compile a drop column command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropColumn(Blueprint $blueprint, Fluent $command) { $columns = $this->prefixArray('drop column', $this->wrapArray($command->columns)); $table = $this->wrapTable($blueprint); return 'alter table ' . $table . ' ' . implode(', ', $columns); } /** * Compile a drop primary key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropPrimary(Blueprint $blueprint, Fluent $command) { $table = $blueprint->getTable(); $index = $this->wrap("{$table}_pkey"); return 'alter table ' . $this->wrapTable($blueprint) . " drop constraint {$index}"; } /** * Compile a drop unique key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropUnique(Blueprint $blueprint, Fluent $command) { $table = $this->wrapTable($blueprint); $index = $this->wrap($command->index); return "alter table {$table} drop constraint {$index}"; } /** * Compile a drop index command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropIndex(Blueprint $blueprint, Fluent $command) { $index = $this->wrap($command->index); return "drop index {$index}"; } /** * Compile a drop foreign key command. * * @param \Illuminate\Database\Schema\Blueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropForeign(Blueprint $blueprint, Fluent $command) { $table = $this->wrapTable($blueprint); $index = $this->wrap($command->index); return "alter table {$table} drop constraint {$index}"; }
      
      







次の内容の移行を作成して実行することにより、クラスのパフォーマンスをテストできます。



 use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateUsersTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('users', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('email')->unique(); $table->string('password'); $table->rememberToken(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('users'); } }
      
      





コマンドで開始した結果:



 php artisan migrate
      
      





次のDDLを使用してテーブルが作成されます。



 CREATE TABLE "users" ( "id" INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, "name" VARCHAR(255) NOT NULL, "email" VARCHAR(255) NOT NULL, "password" VARCHAR(255) NOT NULL, "remember_token" VARCHAR(100), "created_at" TIMESTAMP, "updated_at" TIMESTAMP ); ALTER TABLE "users" ADD CONSTRAINT "users_email_unique" UNIQUE ("email");
      
      





次のコマンドを使用して、移行をロールバックできます。



 php artisan migrate:reset
      
      





シーケンスサポートの追加



テーブルメタデータの作成、変更、削除をサポートするクエリまたはクエリセットを生成するように設計されたBlueprintクラスと同様に、シーケンスの同じアクションをサポートするSequenceBlueprintクラスを作成します。このクラスは非常に単純です。全体を説明するのではなく、同様のブループリントクラスとの主な違いのみを説明します。



Firebirdのシーケンスには、次の属性があります。シーケンス名、初期値、および増分。NEXTVALUE FORステートメントで使用されます。最後の属性は、バージョン3.0以降で使用可能です。したがって、クラスには次のプロパティが含まれます。



  /** * The sequence the blueprint describes. * * @var string */ protected $sequence; /** * Initial sequence value * * @var int */ protected $start_with = 0; /** * Increment for sequence * * @var int */ protected $increment = 1; /** * Restart flag that indicates that the sequence should be reset * * @var bool */ protected $restart = false;
      
      





値を取得してこれらのプロパティを設定する方法は基本的なため、ここでは説明しません。ALTER SEQUENCEステートメントでRESTART句を生成するときに使用される再起動メソッドのみを提供します。



  /** * Restart sequence and set initial value * * @param int $startWith */ public function restart($startWith = null) { $this->restart = true; $this->start_with = $startWith; }
      
      





ブループリントクラスと同様に、作成またはドロップが指定されていない場合、alter sequenceコマンドが実行されます。



  /** * Determine if the blueprint has a create command. * * @return bool */ protected function creating() { foreach ($this->commands as $command) { if ($command->name == 'createSequence') { return true; } } return false; } /** * Determine if the blueprint has a drop command. * * @return bool */ protected function dropping() { foreach ($this->commands as $command) { if ($command->name == 'dropSequence') { return true; } if ($command->name == 'dropSequenceIfExists') { return true; } } return false; } /** * Add the commands that are implied by the blueprint. * * @return void */ protected function addImpliedCommands() { if (($this->restart || ($this->increment !== 1)) && ! $this->creating() && ! $this->dropping()) { array_unshift($this->commands, $this->createCommand('alterSequence')); } } /** * Get the raw SQL statements for the blueprint. * * @param \Illuminate\Database\Connection $connection * @param \Illuminate\Database\Schema\Grammars\Grammar $grammar * @return array */ public function toSql(Connection $connection, Grammar $grammar) { $this->addImpliedCommands(); $statements = []; // Each type of command has a corresponding compiler function on the schema // grammar which is used to build the necessary SQL statements to build // the sequence blueprint element, so we'll just call that compilers function. foreach ($this->commands as $command) { $method = 'compile'.ucfirst($command->name); if (method_exists($grammar, $method)) { if (! is_null($sql = $grammar->$method($this, $command, $connection))) { $statements = array_merge($statements, (array) $sql); } } } return $statements; }
      
      





Illuminate \ Database \ Schema \ SequenceBlueprintクラスの完全なコードは、記事に添付されているソースコードにあります。



今度は、Illuminate \ Database \ Schema \ Grammars \ FirebirdGrammar文法クラスに戻り、{CREATE | ALTER | DROP}シーケンス。



オペレーターサポート{CREATE | ALTER | ドロップ}シーケンス
  /** * Compile a create sequence command. * * @param \Illuminate\Database\Schema\SequenceBlueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileCreateSequence(SequenceBlueprint $blueprint, Fluent $command) { $sql = 'create sequence '; $sql .= $this->wrapSequence($blueprint); if ($blueprint->getInitialValue() !== 0) { $sql .= ' start with ' . $blueprint->getInitialValue(); } if ($blueprint->getIncrement() !== 1) { $sql .= ' increment by ' . $blueprint->getIncrement(); } return $sql; } /** * Compile a alter sequence command. * * @param \Illuminate\Database\Schema\SequenceBlueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileAlterSequence(SequenceBlueprint $blueprint, Fluent $command) { $sql = 'alter sequence '; $sql .= $this->wrapSequence($blueprint); if ($blueprint->isRestart()) { $sql .= ' restart'; if ($blueprint->getInitialValue() !== null) { $sql .= ' with ' . $blueprint->getInitialValue(); } } if ($blueprint->getIncrement() !== 1) { $sql .= ' increment by ' . $blueprint->getIncrement(); } return $sql; } /** * Compile a drop sequence command. * * @param \Illuminate\Database\Schema\SequenceBlueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropSequence(SequenceBlueprint $blueprint, Fluent $command) { return 'drop sequence ' . $this->wrapSequence($blueprint); } /** * Compile a drop sequence command. * * @param \Illuminate\Database\Schema\SequenceBlueprint $blueprint * @param \Illuminate\Support\Fluent $command * @return string */ public function compileDropSequenceIfExists(SequenceBlueprint $blueprint, Fluent $command) { $sql = 'EXECUTE BLOCK' . "\n"; $sql .= 'AS' . "\n"; $sql .= 'BEGIN' . "\n"; $sql .= " IF (EXISTS(select * from RDB\$GENERATORS where RDB\$GENERATOR_NAME = '" . $blueprint->getSequence() . "')) THEN" . "\n"; $sql .= " EXECUTE STATEMENT 'DROP SEQUENCE " . $this->wrapSequence($blueprint) . "';" . "\n"; $sql .= 'END'; return $sql; } /** * Wrap a sequence in keyword identifiers. * * @param mixed $sequence * @return string */ public function wrapSequence($sequence) { if ($sequence instanceof SequenceBlueprint) { $sequence = $sequence->getSequence(); } if ($this->isExpression($sequence)) { return $this->getValue($sequence); } return $this->wrap($this->tablePrefix . $sequence, true); }
      
      







さて、Laravel移行でのシーケンスサポートの追加は完了しました。それがどのように機能するかを見てみましょう。このため、先ほどの移行を少し変更します。



シーケンス上のDDLをテストするための移行
 class CreateUsersTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { //   // CREATE SEQUENCE "seq_users_id" Schema::createSequence('seq_users_id'); //   // CREATE TABLE "users" ( // "id" INTEGER NOT NULL, // "name" VARCHAR(255) NOT NULL, // "email" VARCHAR(255) NOT NULL, // "password" VARCHAR(255) NOT NULL, // "remember_token" VARCHAR(100), // "created_at" TIMESTAMP, // "updated_at" TIMESTAMP // ); // ALTER TABLE "users" ADD PRIMARY KEY ("id"); // ALTER TABLE "users" ADD CONSTRAINT "users_email_unique" UNIQUE ("email"); Schema::create('users', function (Blueprint $table) { //$table->increments('id'); $table->integer('id')->primary(); $table->string('name'); $table->string('email')->unique(); $table->string('password'); $table->rememberToken(); $table->timestamps(); }); //   // ALTER SEQUENCE "seq_users_id" RESTART WITH 10 INCREMENT BY 5 Schema::sequence('seq_users_id', function (SequenceBlueprint $sequence) { $sequence->increment(5); $sequence->restart(10); }); } /** * Reverse the migrations. * * @return void */ public function down() { //   // DROP SEQUENCE "seq_users_id" Schema::dropSequence('seq_users_id'); //   // DROP TABLE "users" Schema::drop('users'); } }
      
      







さらに進んで、BEFORE INSERTトリガーとシーケンス(ジェネレーター)の作成をサポートして、Firebird 2.5以前で自動インクリメントフィールドをサポートできます。ただし、ほとんどの場合、シーケンスの次の値を取得してINSERT要求に渡すだけで十分です。



おわりに



上記の変更は、Laravelフレームワークを使用してFirebird DBMSを使用してWebアプリケーションを開発するのに十分です。もちろん、これをパッケージとして設計し、それを接続して機能を拡張するといいでしょう。これは他のLaravelモジュールで行われます。



次の記事では、LaravelとFirebird DBMSを使用して小さなアプリケーションを作成する方法について説明します。 FirebirdをLaravelフレームワークに統合することに興味がある場合、またはエラーを見つけた場合は、個人的な返信を書いてください。 Laravel for Firebirdサポート用に変更されたファイルは、ここからダウンロードできます



発言



github.com/jacquestvanzuydam/laravel-firebird , Laravel. ellrion .




All Articles