Migrations are a declarative API that helps you transform your database. Each migration
is represented by a PHP class in a unique file. It is preferred that you write
your migrations using the Migrations API, but raw SQL is also supported.
Adding Columns
Column types are specified as strings and can be one of:
binary
boolean
char
date
datetime
decimal
float
double
smallinteger
integer
biginteger
string
text
time
timestamp
uuid
binaryuuid
nativeuuid
In addition, the MySQL adapter supports enum, set, blob,
tinyblob, mediumblob, longblob, bit and json column types
(json in MySQL 5.7 and above). When providing a limit value and using
binary, varbinary or blob and its subtypes, the retained column type
will be based on required length (see Limit Option and MySQL for details).
With most adapters, the uuid and nativeuuid column types are aliases,
however with the MySQL adapter + MariaDB, the nativeuuid type maps to
a native uuid column instead of CHAR(36) like uuid does.
In addition, the Postgres adapter supports interval, json, jsonb,
uuid, cidr, inet and macaddr column types (PostgreSQL 9.3 and
above).
Valid Column Options
The following are valid column options:
For any column type:
Option |
Description |
limit |
set maximum length for strings, also hints column types in adapters (see note below) |
length |
alias for limit |
default |
set default value or action |
null |
allow NULL values, defaults to true (setting identity will override default to false) |
after |
specify the column that a new column should be placed after, or use \Migrations\Db\Adapter\MysqlAdapter::FIRST to place the column at the start of the table (only applies to MySQL) |
comment |
set a text comment on the column |
For decimal and float columns:
Option |
Description |
precision |
total number of digits (e.g., 10 in DECIMAL(10,2)) |
scale |
number of digits after the decimal point (e.g., 2 in DECIMAL(10,2)) |
signed |
enable or disable the unsigned option (only applies to MySQL) |
Note
Precision and Scale Terminology
Migrations follows the SQL standard where precision represents the total number of digits,
and scale represents digits after the decimal point. For example, to create DECIMAL(10,2)
(10 total digits with 2 decimal places):
$table->addColumn('price', 'decimal', [
'precision' => 10, // Total digits
'scale' => 2, // Decimal places
]);
This differs from CakePHP’s TableSchema which uses length for total digits and
precision for decimal places. The migration adapter handles this conversion automatically.
For enum and set columns:
Option |
Description |
values |
Can be a comma separated list or an array of values |
For smallinteger, integer and biginteger columns:
For Postgres, when using identity, it will utilize the serial type
appropriate for the integer size, so that smallinteger will give you
smallserial, integer gives serial, and biginteger gives
bigserial.
For timestamp columns:
Option |
Description |
default |
set default value (use with CURRENT_TIMESTAMP) |
update |
set an action to be triggered when the row is updated (use with CURRENT_TIMESTAMP) (only applies to MySQL) |
timezone |
enable or disable the with time zone option for time and timestamp columns (only applies to Postgres) |
You can add created and updated timestamps to a table using the
addTimestamps() method. This method accepts three arguments, where the first
two allow setting alternative names for the columns while the third argument
allows you to enable the timezone option for the columns. The defaults for
these arguments are created, updated, and false respectively. For
the first and second argument, if you provide null, then the default name
will be used, and if you provide false, then that column will not be
created. Please note that attempting to set both to false will throw
a \RuntimeException. Additionally, you can use the
addTimestampsWithTimezone() method, which is an alias to addTimestamps()
that will always set the third argument to true (see examples below). The
created column will have a default set to CURRENT_TIMESTAMP. For MySQL
only, updated column will have update set to
CURRENT_TIMESTAMP:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
/**
* Migrate Change.
*/
public function change(): void
{
// Use defaults (without timezones)
$table = $this->table('users')->addTimestamps()->create();
// Use defaults (with timezones)
$table = $this->table('users')->addTimestampsWithTimezone()->create();
// Override the 'created' column name with 'recorded_at'.
$table = $this->table('books')->addTimestamps('recorded_at')->create();
// Override the 'updated' column name with 'amended_at', preserving timezones.
// The two lines below do the same, the second one is simply cleaner.
$table = $this->table('books')->addTimestamps(null, 'amended_at', true)->create();
$table = $this->table('users')->addTimestampsWithTimezone(null, 'amended_at')->create();
// Only add the created column to the table
$table = $this->table('books')->addTimestamps(null, false);
// Only add the updated column to the table
$table = $this->table('users')->addTimestamps(false);
// Note, setting both false will throw a \RuntimeError
}
}
For boolean columns:
Option |
Description |
signed |
enable or disable the unsigned option (only applies to MySQL) |
For string and text columns:
Option |
Description |
collation |
set collation that differs from table defaults (only applies to MySQL) |
encoding |
set character set that differs from table defaults (only applies to MySQL) |
Limit Option and MySQL
When using the MySQL adapter, there are a couple things to consider when working with limits:
When using a string primary key or index on MySQL 5.7 or below, or the
MyISAM storage engine, and the default charset of utf8mb4_unicode_ci, you
must specify a limit less than or equal to 191, or use a different charset.
Additional hinting of database column type can be made for integer,
text, blob, tinyblob, mediumblob, longblob columns. Using
limit with one the following options will modify the column type
accordingly:
Limit |
Column Type |
BLOB_TINY |
TINYBLOB |
BLOB_REGULAR |
BLOB |
BLOB_MEDIUM |
MEDIUMBLOB |
BLOB_LONG |
LONGBLOB |
TEXT_TINY |
TINYTEXT |
TEXT_REGULAR |
TEXT |
TEXT_MEDIUM |
MEDIUMTEXT |
TEXT_LONG |
LONGTEXT |
INT_TINY |
TINYINT |
INT_SMALL |
SMALLINT |
INT_MEDIUM |
MEDIUMINT |
INT_REGULAR |
INT |
INT_BIG |
BIGINT |
For binary or varbinary types, if limit is set greater than allowed 255
bytes, the type will be changed to the best matching blob type given the
length:
<?php
use Migrations\Db\Adapter\MysqlAdapter;
//...
$table = $this->table('cart_items');
$table->addColumn('user_id', 'integer')
->addColumn('product_id', 'integer', ['limit' => MysqlAdapter::INT_BIG])
->addColumn('subtype_id', 'integer', ['limit' => MysqlAdapter::INT_SMALL])
->addColumn('quantity', 'integer', ['limit' => MysqlAdapter::INT_TINY])
->create();
Default values with expressions
If you need to set a default to an expression, you can use a Literal to have
the column’s default value used without any quoting or escaping. This is helpful
when you want to use a function as a default value:
use Migrations\BaseMigration;
use Migrations\Db\Literal;
class AddSomeColumns extends BaseMigration
{
public function change(): void
{
$this->table('users')
->addColumn('uniqid', 'uuid', [
'default' => Literal::from('uuid_generate_v4()')
])
->create();
}
}
Creating a Table
Creating a table is really easy using the Table object. Let’s create a table to
store a collection of users:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
public function change(): void
{
$users = $this->table('users');
$users->addColumn('username', 'string', ['limit' => 20])
->addColumn('password', 'string', ['limit' => 40])
->addColumn('password_salt', 'string', ['limit' => 40])
->addColumn('email', 'string', ['limit' => 100])
->addColumn('first_name', 'string', ['limit' => 30])
->addColumn('last_name', 'string', ['limit' => 30])
->addColumn('created', 'datetime')
->addColumn('updated', 'datetime', ['null' => true])
->addIndex(['username', 'email'], ['unique' => true])
->create();
}
}
Columns are added using the addColumn() method. We create a unique index
for both the username and email columns using the addIndex() method.
Finally calling create() commits the changes to the database.
Note
Migrations automatically creates an auto-incrementing primary key column called id for every
table.
The id option sets the name of the automatically created identity field,
while the primary_key option selects the field or fields used for primary
key. id will always override the primary_key option unless it’s set to
false. If you don’t need a primary key set id to false without specifying
a primary_key, and no primary key will be created.
To specify an alternate primary key, you can specify the primary_key option
when accessing the Table object. Let’s disable the automatic id column and
create a primary key using two columns instead:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
public function change(): void
{
$table = $this->table('followers', ['id' => false, 'primary_key' => ['user_id', 'follower_id']]);
$table->addColumn('user_id', 'integer')
->addColumn('follower_id', 'integer')
->addColumn('created', 'datetime')
->create();
}
}
Setting a single primary_key doesn’t enable the AUTO_INCREMENT option.
To simply change the name of the primary key, we need to override the default id field name:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
public function up(): void
{
$table = $this->table('followers', ['id' => 'user_id']);
$table->addColumn('follower_id', 'integer')
->addColumn('created', 'timestamp', ['default' => 'CURRENT_TIMESTAMP'])
->create();
}
}
In addition, the MySQL adapter supports following options:
Option |
Platform |
Description |
comment |
MySQL, Postgres |
set a text comment on the table |
collation |
MySQL, SqlServer |
the default collation for a table if different than the database. |
row_format |
MySQL |
set the table row format |
engine |
MySQL |
define table engine (defaults to ``InnoDB``) |
collation |
MySQL |
define table collation (defaults to ``utf8mb4_unicode_ci``) |
signed |
MySQL |
whether the primary key is signed (defaults to ``false``) |
limit |
MySQL |
set the maximum length for the primary key |
By default, the primary key is unsigned.
To simply set it to be signed just pass signed option with a true
value:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
public function change(): void
{
$table = $this->table('followers', ['signed' => false]);
$table->addColumn('follower_id', 'integer')
->addColumn('created', 'timestamp', ['default' => 'CURRENT_TIMESTAMP'])
->create();
}
}
If you need to create a table with a different collation than the database,
use:
<?php
use Migrations\BaseMigration;
class CreateCategoriesTable extends BaseMigration
{
public function change(): void
{
$table = $this
->table('categories', [
'collation' => 'latin1_german1_ci'
])
->addColumn('title', 'string')
->create();
}
}
Note however this can only be done on table creation : there is currently no way
of adding a column to an existing table with a different collation than the
table or the database. Only MySQL and SqlServer supports this
configuration key for the time being.
To view available column types and options, see Adding Columns for details.
Saving Changes
When working with the Table object, Migrations stores certain operations in a
pending changes cache. Once you have made the changes you want to the table,
you must save them. To perform this operation, Migrations provides three methods,
create(), update(), and save(). create() will first create
the table and then run the pending changes. update() will just run the
pending changes, and should be used when the table already exists. save()
is a helper function that checks first if the table exists and if it does not
will run create(), else it will run update().
As stated above, when using the change() migration method, you should always
use create() or update(), and never save() as otherwise migrating
and rolling back may result in different states, due to save() calling
create() when running migrate and then update() on rollback. When
using the up()/down() methods, it is safe to use either save() or
the more explicit methods.
When in doubt with working with tables, it is always recommended to call
the appropriate function and commit any pending changes to the database.
Renaming a Column
To rename a column, access an instance of the Table object then call the
renameColumn() method:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
/**
* Migrate Up.
*/
public function up(): void
{
$table = $this->table('users');
$table->renameColumn('bio', 'biography')
->save();
}
/**
* Migrate Down.
*/
public function down(): void
{
$table = $this->table('users');
$table->renameColumn('biography', 'bio')
->save();
}
}
Adding a Column After Another Column
When adding a column with the MySQL adapter, you can dictate its position using
the after option, where its value is the name of the column to position it
after:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
/**
* Change Method.
*/
public function change(): void
{
$table = $this->table('users');
$table->addColumn('city', 'string', ['after' => 'email'])
->update();
}
}
This would create the new column city and position it after the email
column. The \Migrations\Db\Adapter\MysqlAdapter::FIRST constant can be used
to specify that the new column should be created as the first column in that
table.
Dropping a Column
To drop a column, use the removeColumn() method:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
/**
* Migrate up.
*/
public function up(): void
{
$table = $this->table('users');
$table->removeColumn('short_name')
->save();
}
}
Specifying a Column Limit
You can limit the maximum length of a column by using the limit option:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
/**
* Change Method.
*/
public function change(): void
{
$table = $this->table('tags');
$table->addColumn('short_name', 'string', ['limit' => 30])
->update();
}
}
Changing Column Attributes
To change column type or options on an existing column, use the changeColumn() method.
See valid-column-types and Valid Column Options for allowed values:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
/**
* Migrate Up.
*/
public function up(): void
{
$users = $this->table('users');
$users->changeColumn('email', 'string', ['limit' => 255])
->save();
}
/**
* Migrate Down.
*/
public function down(): void
{
}
}
Working With Indexes
To add an index to a table you can simply call the addIndex() method on the
table object:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
/**
* Migrate Up.
*/
public function up(): void
{
$table = $this->table('users');
$table->addColumn('city', 'string')
->addIndex(['city'])
->save();
}
/**
* Migrate Down.
*/
public function down(): void
{
}
}
By default Migrations instructs the database adapter to create a simple index. We
can pass an additional parameter unique to the addIndex() method to
specify a unique index. We can also explicitly specify a name for the index
using the name parameter, the index columns sort order can also be specified using
the order parameter. The order parameter takes an array of column names and sort order key/value pairs:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
/**
* Migrate Up.
*/
public function up(): void
{
$table = $this->table('users');
$table->addColumn('email', 'string')
->addColumn('username','string')
->addIndex(['email', 'username'], [
'unique' => true,
'name' => 'idx_users_email',
'order' => ['email' => 'DESC', 'username' => 'ASC']]
)
->save();
}
}
As of 4.6.0, you can use BaseMigration::index() to get a fluent builder to
define indexes:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
/**
* Migrate Up.
*/
public function up(): void
{
$table = $this->table('users');
$table->addColumn('email', 'string')
->addColumn('username','string')
->addIndex(
$this->index(['email', 'username'])
->setType('unique')
->setName('idx_users_email')
->setOrder(['email' => 'DESC', 'username' => 'ASC'])
)
->save();
}
}
The MySQL adapter also supports fulltext indexes. If you are using a version before 5.6 you must
ensure the table uses the MyISAM engine:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
public function change(): void
{
$table = $this->table('users', ['engine' => 'MyISAM']);
$table->addColumn('email', 'string')
->addIndex('email', ['type' => 'fulltext'])
->create();
}
}
MySQL adapter supports setting the index length defined by limit option.
When you are using a multi-column index, you are able to define each column index length.
The single column index can define its index length with or without defining column name in limit option:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
public function change(): void
{
$table = $this->table('users');
$table->addColumn('email', 'string')
->addColumn('username','string')
->addColumn('user_guid', 'string', ['limit' => 36])
->addIndex(['email','username'], ['limit' => ['email' => 5, 'username' => 2]])
->addIndex('user_guid', ['limit' => 6])
->create();
}
}
The SQL Server and PostgreSQL adapters support include (non-key) columns on indexes:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
public function change(): void
{
$table = $this->table('users');
$table->addColumn('email', 'string')
->addColumn('firstname','string')
->addColumn('lastname','string')
->addIndex(['email'], ['include' => ['firstname', 'lastname']])
->create();
}
}
PostgreSQL, SQLServer, and SQLite support partial indexes by defining where
clauses for the index:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
public function change(): void
{
$table = $this->table('users');
$table->addColumn('email', 'string')
->addColumn('is_verified','boolean')
->addIndex(
$this->index('email')
->setName('user_email_verified_idx')
->setType('unique')
->setWhere('is_verified = true')
)
->create();
}
}
PostgreSQL can create indexes concurrently which avoids taking disruptive locks
during index creation:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
public function change(): void
{
$table = $this->table('users');
$table->addColumn('email', 'string')
->addIndex(
$this->index('email')
->setName('user_email_unique_idx')
->setType('unique')
->setConcurrently(true)
)
->create();
}
}
PostgreSQL adapters also supports Generalized Inverted Index gin indexes:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
public function change(): void
{
$table = $this->table('users');
$table->addColumn('address', 'string')
->addIndex('address', ['type' => 'gin'])
->create();
}
}
Removing indexes is as easy as calling the removeIndex() method. You must
call this method for each index:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
/**
* Migrate Up.
*/
public function up(): void
{
$table = $this->table('users');
$table->removeIndex(['email'])
->save();
// alternatively, you can delete an index by its name, ie:
$table->removeIndexByName('idx_users_email')
->save();
}
/**
* Migrate Down.
*/
public function down(): void
{
}
}
New in version 4.6.0: Index::setWhere(), and Index::setConcurrently() were added.
Working With Foreign Keys
Migrations has support for creating foreign key constraints on your database tables.
Let’s add a foreign key to an example table:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
/**
* Migrate Up.
*/
public function up(): void
{
$table = $this->table('tags');
$table->addColumn('tag_name', 'string')
->save();
$refTable = $this->table('tag_relationships');
$refTable->addColumn('tag_id', 'integer', ['null' => true])
->addForeignKey(
'tag_id',
'tags',
'id',
['delete'=> 'SET_NULL', 'update'=> 'NO_ACTION'],
)
->save();
}
/**
* Migrate Down.
*/
public function down(): void
{
}
}
The ‘delete’ and ‘update’ options allow you to define the ON UPDATE and ON
DELETE behavior. Possibles values are ‘SET_NULL’, ‘NO_ACTION’, ‘CASCADE’ and
‘RESTRICT’. If ‘SET_NULL’ is used then the column must be created as nullable
with the option ['null' => true].
Foreign keys can be defined with arrays of columns to build constraints between
tables with composite keys:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
public function up(): void
{
$table = $this->table('follower_events');
$table->addColumn('user_id', 'integer')
->addColumn('follower_id', 'integer')
->addColumn('event_id', 'integer')
->addForeignKey(
['user_id', 'follower_id'],
'followers',
['user_id', 'follower_id'],
[
'delete'=> 'NO_ACTION',
'update'=> 'NO_ACTION',
'constraint' => 'user_follower_id',
]
)
->save();
}
}
The options parameter of addForeignKey() supports the following options:
Option |
Description |
update |
set an action to be triggered when the row is updated |
delete |
set an action to be triggered when the row is deleted |
constraint |
set a name to be used by foreign key constraint |
deferrable |
define deferred constraint application (postgres only) |
Using the foreignKey() method provides a fluent builder to define a foreign
key:
<?php
use Migrations\BaseMigration;
use Migrations\Db\Table\ForeignKey;
class MyNewMigration extends BaseMigration
{
/**
* Migrate Up.
*/
public function up(): void
{
$table = $this->table('articles');
$table->addForeignKey(
$this->foreignKey()
->setColumns('user_id')
->setReferencedTable('users')
->setReferencedColumns('user_id')
->setDelete(ForeignKey::CASCADE)
->setName('article_user_fk')
)
->save();
}
}
New in version 4.6.0: The foreignKey method was added.
We can also easily check if a foreign key exists:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
/**
* Migrate Up.
*/
public function up(): void
{
$table = $this->table('tag_relationships');
$exists = $table->hasForeignKey('tag_id');
if ($exists) {
// do something
}
}
/**
* Migrate Down.
*/
public function down(): void
{
}
}
Finally, to delete a foreign key, use the dropForeignKey method.
Note that like other methods in the Table class, dropForeignKey also
needs save() to be called at the end in order to be executed. This allows
Migrations to intelligently plan migrations when more than one table is
involved:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
/**
* Migrate Up.
*/
public function up(): void
{
$table = $this->table('tag_relationships');
$table->dropForeignKey('tag_id')->save();
}
/**
* Migrate Down.
*/
public function down(): void
{
}
}
Determining Whether a Table Exists
You can determine whether or not a table exists by using the hasTable()
method:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
/**
* Migrate Up.
*/
public function up(): void
{
$exists = $this->hasTable('users');
if ($exists) {
// do something
}
}
/**
* Migrate Down.
*/
public function down(): void
{
}
}
Dropping a Table
Tables can be dropped quite easily using the drop() method. It is a
good idea to recreate the table again in the down() method.
Note that like other methods in the Table class, drop also needs save()
to be called at the end in order to be executed. This allows Migrations to intelligently
plan migrations when more than one table is involved:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
/**
* Migrate Up.
*/
public function up(): void
{
$this->table('users')->drop()->save();
}
/**
* Migrate Down.
*/
public function down(): void
{
$users = $this->table('users');
$users->addColumn('username', 'string', ['limit' => 20])
->addColumn('password', 'string', ['limit' => 40])
->addColumn('password_salt', 'string', ['limit' => 40])
->addColumn('email', 'string', ['limit' => 100])
->addColumn('first_name', 'string', ['limit' => 30])
->addColumn('last_name', 'string', ['limit' => 30])
->addColumn('created', 'datetime')
->addColumn('updated', 'datetime', ['null' => true])
->addIndex(['username', 'email'], ['unique' => true])
->save();
}
}
Renaming a Table
To rename a table access an instance of the Table object then call the
rename() method:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
/**
* Migrate Up.
*/
public function up(): void
{
$table = $this->table('users');
$table
->rename('legacy_users')
->update();
}
/**
* Migrate Down.
*/
public function down(): void
{
$table = $this->table('legacy_users');
$table
->rename('users')
->update();
}
}
Changing the Primary Key
To change the primary key on an existing table, use the changePrimaryKey()
method. Pass in a column name or array of columns names to include in the
primary key, or null to drop the primary key. Note that the mentioned
columns must be added to the table, they will not be added implicitly:
<?php
use Migrations\BaseMigration;
class MyNewMigration extends BaseMigration
{
/**
* Migrate Up.
*/
public function up(): void
{
$users = $this->table('users');
$users
->addColumn('username', 'string', ['limit' => 20, 'null' => false])
->addColumn('password', 'string', ['limit' => 40])
->save();
$users
->addColumn('new_id', 'integer', ['null' => false])
->changePrimaryKey(['new_id', 'username'])
->save();
}
/**
* Migrate Down.
*/
public function down(): void
{
}
}
Creating Custom Primary Keys
You can specify a autoId property in the Migration class and set it to
false, which will turn off the automatic id column creation. You will
need to manually create the column that will be used as a primary key and add
it to the table declaration:
<?php
use Migrations\BaseMigration;
class CreateProductsTable extends BaseMigration
{
public bool $autoId = false;
public function up(): void
{
$table = $this->table('products');
$table
->addColumn('id', 'uuid')
->addPrimaryKey('id')
->addColumn('name', 'string')
->addColumn('description', 'text')
->create();
}
}
The above will create a CHAR(36) id column that is also the primary key.
When specifying a custom primary key on the command line, you must note
it as the primary key in the id field, otherwise you may get an error
regarding duplicate id fields, i.e.:
bin/cake bake migration CreateProducts id:uuid:primary name:string description:text created modified
All baked migrations and snapshot will use this new way when necessary.
Warning
Dealing with primary key can only be done on table creation operations.
This is due to limitations for some database servers the plugin supports.