diff --git a/CHANGELOG.md b/CHANGELOG.md index 2527dec8..77958fea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## [3.0.0] - 2025-03-30 +## [3.0.0] - 2025-12-05 ### Changed @@ -9,6 +9,7 @@ #### Breaking Changes +- Using `*increments()` for primary key definition now requires adding `.primay()` to the column definition. - Changed raw expressions placeholder from requiringing explicit quoring per grammar (like this '?') to automaic (like this ?) - Changed `update` and `delete` methods to return the affected rows instead of the model - Seeding depencies are now in a separate`[seeder]` extension @@ -16,8 +17,12 @@ ### Fixed - - Model `update` and `delete` not casting passed values +- QueryBuilder does not require `database.py` when passing connection detain in directly +- Import DB (ConnectionResolver) from config even if inline connection details are provided +- `*increments()` can be used fir non primary key columns if supported by the platform +- `*increments()` can be used fir non primary key columns if supported by the platform + - Model `update` and `delete` not casting passed values ## [2.24.0] - 2025-01-23 diff --git a/databases/migrations/2018_01_09_043202_create_users_table.py b/databases/migrations/2018_01_09_043202_create_users_table.py index 218419b9..d2c09516 100644 --- a/databases/migrations/2018_01_09_043202_create_users_table.py +++ b/databases/migrations/2018_01_09_043202_create_users_table.py @@ -6,23 +6,21 @@ class CreateUsersTable(Migration): def up(self): """Run the migrations.""" - with self.schema.create('users') as table: - table.increments('id') - table.string('name') - table.string('email').unique() - table.string('password') - table.string('second_password').nullable() - table.string('remember_token').nullable() - table.timestamp('verified_at').nullable() + with self.schema.create("users") as table: + table.increments("id").primary() + table.string("name") + table.string("email").unique() + table.string("password") + table.string("second_password").nullable() + table.string("remember_token").nullable() + table.timestamp("verified_at").nullable() table.timestamps() if not self.schema._dry: - User.on(self.connection).set_schema(self.schema_name).create({ - 'name': 'Joe', - 'email': 'joe@email.com', - 'password': 'secret' - }) + User.on(self.connection).set_schema(self.schema_name).create( + {"name": "Joe", "email": "joe@email.com", "password": "secret"} + ) def down(self): """Revert the migrations.""" - self.schema.drop('users') + self.schema.drop("users") diff --git a/databases/migrations/2020_04_17_000000_create_friends_table.py b/databases/migrations/2020_04_17_000000_create_friends_table.py index 425f5113..2df9ab46 100644 --- a/databases/migrations/2020_04_17_000000_create_friends_table.py +++ b/databases/migrations/2020_04_17_000000_create_friends_table.py @@ -1,19 +1,20 @@ from src.masoniteorm.migrations.Migration import Migration + class CreateFriendsTable(Migration): - def up(self): + def up(self): """ Run the migrations. """ - with self.schema.create('friends') as table: - table.increments('id') - table.string('name') - table.integer('age') + with self.schema.create("friends") as table: + table.increments("id").primary() + table.string("name") + table.integer("age") def down(self): """ Revert the migrations. """ - self.schema.drop('friends') \ No newline at end of file + self.schema.drop("friends") diff --git a/databases/migrations/2020_04_17_00000_create_articles_table.py b/databases/migrations/2020_04_17_00000_create_articles_table.py index 8fe7f70c..754608ea 100644 --- a/databases/migrations/2020_04_17_00000_create_articles_table.py +++ b/databases/migrations/2020_04_17_00000_create_articles_table.py @@ -1,18 +1,19 @@ from src.masoniteorm.migrations.Migration import Migration + class CreateArticlesTable(Migration): - def up(self): + def up(self): """ Run the migrations. """ - with self.schema.create('fans') as table: - table.increments('id') - table.string('name') - table.integer('age') + with self.schema.create("fans") as table: + table.increments("id").primary() + table.string("name") + table.integer("age") def down(self): """ Revert the migrations. """ - self.schema.drop('fans') \ No newline at end of file + self.schema.drop("fans") diff --git a/databases/migrations/2020_10_20_152904_create_table_schema_migration.py b/databases/migrations/2020_10_20_152904_create_table_schema_migration.py index 63268b9e..126b8789 100644 --- a/databases/migrations/2020_10_20_152904_create_table_schema_migration.py +++ b/databases/migrations/2020_10_20_152904_create_table_schema_migration.py @@ -10,8 +10,8 @@ def up(self): Run the migrations. """ with self.schema.create("table_schema") as table: - table.increments('id') - table.string('name') + table.increments("id").primary() + table.string("name") table.timestamps() def down(self): diff --git a/orm.sqlite3 b/orm.sqlite3 index cdda25e6..e23f9878 100644 Binary files a/orm.sqlite3 and b/orm.sqlite3 differ diff --git a/src/masoniteorm/migrations/Migration.py b/src/masoniteorm/migrations/Migration.py index 51291e5d..db41d58c 100644 --- a/src/masoniteorm/migrations/Migration.py +++ b/src/masoniteorm/migrations/Migration.py @@ -46,7 +46,7 @@ def __init__( def create_table_if_not_exists(self): if not self.schema.has_table("migrations"): with self.schema.create("migrations") as table: - table.increments("migration_id") + table.increments("migration_id").primary() table.string("migration") table.integer("batch") diff --git a/src/masoniteorm/schema/Blueprint.py b/src/masoniteorm/schema/Blueprint.py index 05089501..f1171028 100644 --- a/src/masoniteorm/schema/Blueprint.py +++ b/src/masoniteorm/schema/Blueprint.py @@ -161,7 +161,7 @@ def _compile_alter(self): )._compile_create() def increments(self, column, nullable=False): - """Sets a column to be the auto incrementing primary key representation for the table. + """Sets a column to be the auto-incrementing integer. Arguments: column {string} -- The column name. @@ -176,11 +176,10 @@ def increments(self, column, nullable=False): column, "increments", nullable=nullable ) - self.primary(column) return self def tiny_increments(self, column, nullable=False): - """Sets a column to be the auto tiny incrementing primary key representation for the table. + """Sets a column to an auto-increment tiny integer. Arguments: column {string} -- The column name. @@ -195,11 +194,10 @@ def tiny_increments(self, column, nullable=False): column, "tiny_increments", nullable=nullable ) - self.primary(column) return self def id(self, column="id"): - """Sets a column to be the auto-incrementing big integer (8-byte) primary key representation for the table. + """Sets a column to an auto-incrementing big integer. Arguments: column {string} -- The column name. Defaults to "id". @@ -207,7 +205,7 @@ def id(self, column="id"): Returns: self """ - return self.big_increments(column) + return self.big_increments(column).primary() def uuid(self, column, nullable=False, length=36): """Sets a column to be the UUID4 representation for the table. @@ -227,7 +225,7 @@ def uuid(self, column, nullable=False, length=36): return self def big_increments(self, column, nullable=False): - """Sets a column to be the the big integer increments representation for the table + """Sets a column to an auto-incrementing big integer Arguments: column {string} -- The column name. @@ -242,7 +240,6 @@ def big_increments(self, column, nullable=False): column, "big_increments", nullable=nullable ) - self.primary(column) return self def binary(self, column, nullable=False): @@ -905,6 +902,24 @@ def primary(self, column=None, name=None): column = self._last_column.name if not isinstance(column, list): + self.table.set_primary_key(column) + check_column = self.table.added_columns.get(column) + if check_column: + check_column.set_as_primary() + if check_column.column_type in [ + "tiny_increments", + "increments", + "big_increments", + ]: + # use column attributes for primary key auto increment columns + check_column.column_type = ( + f"{check_column.column_type}_primary" + ) + self.table.added_columns[column] = check_column + return self + + self.table.added_columns[column] = check_column + column = [column] self.table.add_constraint( diff --git a/src/masoniteorm/schema/Schema.py b/src/masoniteorm/schema/Schema.py index eaedabf8..3b2499e5 100644 --- a/src/masoniteorm/schema/Schema.py +++ b/src/masoniteorm/schema/Schema.py @@ -1,4 +1,5 @@ from ..config import load_config +from ..connections import ConnectionResolver from ..exceptions import ConnectionNotRegistered from .Blueprint import Blueprint from .Table import Table @@ -87,8 +88,14 @@ def on(self, connection_key): Returns: cls """ - resolver = load_config(config_path=self.config_path).DB - self.connection_details = resolver.get_connection_details() + if not self.connection_details: + resolver = ConnectionResolver( + connection_details=self.connection_details + ) + else: + resolver = load_config(config_path=self.config_path).DB + self.connection_details = resolver.get_connection_details() + if connection_key == "default": self.connection = self.connection_details.get("default") else: diff --git a/src/masoniteorm/schema/platforms/MSSQLPlatform.py b/src/masoniteorm/schema/platforms/MSSQLPlatform.py index 0ee57b2a..1f6db4bd 100644 --- a/src/masoniteorm/schema/platforms/MSSQLPlatform.py +++ b/src/masoniteorm/schema/platforms/MSSQLPlatform.py @@ -14,7 +14,6 @@ class MSSQLPlatform(Platform): type_map = { "string": "VARCHAR", "char": "CHAR", - "big_increments": "BIGINT IDENTITY", "integer": "INT", "big_integer": "BIGINT", "tiny_integer": "TINYINT", @@ -25,7 +24,6 @@ class MSSQLPlatform(Platform): "tiny_integer_unsigned": "TINYINT", "small_integer_unsigned": "SMALLINT", "medium_integer_unsigned": "MEDIUMINT", - "increments": "INT IDENTITY", "uuid": "CHAR", "binary": "LONGBLOB", "boolean": "BOOLEAN", @@ -48,9 +46,14 @@ class MSSQLPlatform(Platform): "date": "DATE", "year": "YEAR", "datetime": "DATETIME", - "tiny_increments": "TINYINT IDENTITY", "unsigned": "INT", "unsigned_integer": "INT", + "tiny_increments": "TINYINT IDENTITY", + "increments": "INT IDENTITY", + "big_increments": "BIGINT IDENTITY", + "tiny_increments_primary": "TINYINT PRIMARY KEY IDENTITY", + "increments_primary": "INT PRIMARY KEY IDENTITY", + "big_increments_primary": "BIGINT PRIMARY KEY IDENTITY", } premapped_nulls = {True: "NULL", False: "NOT NULL"} @@ -261,9 +264,6 @@ def columnize(self, columns): constraint = "" column_constraint = "" - if column.primary: - constraint = " PRIMARY KEY" - if column.column_type == "enum": values = ", ".join(f"'{x}'" for x in column.values) column_constraint = f" CHECK([{column.name}] IN ({values}))" diff --git a/src/masoniteorm/schema/platforms/MySQLPlatform.py b/src/masoniteorm/schema/platforms/MySQLPlatform.py index 23206274..935b0787 100644 --- a/src/masoniteorm/schema/platforms/MySQLPlatform.py +++ b/src/masoniteorm/schema/platforms/MySQLPlatform.py @@ -21,8 +21,6 @@ class MySQLPlatform(Platform): "tiny_integer_unsigned": "TINYINT UNSIGNED", "small_integer_unsigned": "SMALLINT UNSIGNED", "medium_integer_unsigned": "MEDIUMINT UNSIGNED", - "big_increments": "BIGINT UNSIGNED AUTO_INCREMENT", - "increments": "INT UNSIGNED AUTO_INCREMENT", "uuid": "CHAR", "binary": "LONGBLOB", "boolean": "BOOLEAN", @@ -45,8 +43,13 @@ class MySQLPlatform(Platform): "date": "DATE", "year": "YEAR", "datetime": "DATETIME", - "tiny_increments": "TINYINT AUTO_INCREMENT", "unsigned": "INT UNSIGNED", + "tiny_increments": "TINYINT UNSIGNED AUTO_INCREMENT", + "increments": "INT UNSIGNED AUTO_INCREMENT", + "big_increments": "BIGINT UNSIGNED AUTO_INCREMENT", + "tiny_increments_primary": "TINYINT UNSIGNED PRIMARY KEY AUTO_INCREMENT", + "increments_primary": "INT UNSIGNED PRIMARY KEY AUTO_INCREMENT", + "big_increments_primary": "BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT", } premapped_nulls = {True: "NULL", False: "NOT NULL"} @@ -88,9 +91,6 @@ def columnize(self, columns): constraint = "" column_constraint = "" - if column.primary: - constraint = "PRIMARY KEY" - if column.column_type == "enum": values = ", ".join(f"'{x}'" for x in column.values) column_constraint = f"({values})" diff --git a/src/masoniteorm/schema/platforms/Platform.py b/src/masoniteorm/schema/platforms/Platform.py index c39d65dd..677ad5f7 100644 --- a/src/masoniteorm/schema/platforms/Platform.py +++ b/src/masoniteorm/schema/platforms/Platform.py @@ -1,6 +1,5 @@ class Platform: foreign_key_actions = { - "cascade": "CASCADE", "set null": "SET NULL", "cascade": "CASCADE", "restrict": "RESTRICT", @@ -11,44 +10,7 @@ class Platform: signed = {"signed": "SIGNED", "unsigned": "UNSIGNED"} def columnize(self, columns): - sql = [] - for name, column in columns.items(): - if column.length: - length = self.create_column_length(column.column_type).format( - length=column.length - ) - else: - length = "" - - if column.default in (0,): - default = f" DEFAULT {column.default}" - elif column.default in self.premapped_defaults.keys(): - default = self.premapped_defaults.get(column.default) - elif column.default: - if ( - isinstance(column.default, (str,)) - and not column.default_is_raw - ): - default = f" DEFAULT '{column.default}'" - else: - default = f" DEFAULT {column.default}" - else: - default = "" - - sql.append( - self.columnize_string() - .format( - name=column.name, - data_type=self.type_map.get(column.column_type, ""), - length=length, - constraint="PRIMARY KEY" if column.primary else "", - nullable=self.premapped_nulls.get(column.is_null) or "", - default=default, - ) - .strip() - ) - - return sql + raise NotImplementedError def columnize_string(self): raise NotImplementedError diff --git a/src/masoniteorm/schema/platforms/PostgresPlatform.py b/src/masoniteorm/schema/platforms/PostgresPlatform.py index 3b0184de..bf8facb9 100644 --- a/src/masoniteorm/schema/platforms/PostgresPlatform.py +++ b/src/masoniteorm/schema/platforms/PostgresPlatform.py @@ -22,7 +22,6 @@ class PostgresPlatform(Platform): "integer": "INTEGER", "big_integer": "BIGINT", "tiny_integer": "TINYINT", - "big_increments": "BIGSERIAL UNIQUE", "small_integer": "SMALLINT", "medium_integer": "MEDIUMINT", # Postgres database does not implement unsigned types @@ -32,7 +31,6 @@ class PostgresPlatform(Platform): "tiny_integer_unsigned": "TINYINT", "small_integer_unsigned": "SMALLINT", "medium_integer_unsigned": "MEDIUMINT", - "increments": "SERIAL UNIQUE", "uuid": "UUID", "binary": "BYTEA", "boolean": "BOOLEAN", @@ -55,8 +53,13 @@ class PostgresPlatform(Platform): "date": "DATE", "year": "YEAR", "datetime": "TIMESTAMPTZ", - "tiny_increments": "TINYINT AUTO_INCREMENT", "unsigned": "INT", + "tiny_increments": "SMALLSERIAL", + "increments": "SERIAL", + "big_increments": "BIGSERIAL", + "tiny_increments_primary": "SMALLSERIAL PRIMARY KEY", + "increments_primary": "SERIAL PRIMARY KEY", + "big_increments_primary": "BIGSERIAL PRIMARY KEY", } table_info_map = { @@ -161,9 +164,6 @@ def columnize(self, columns): constraint = "" column_constraint = "" - if column.primary: - constraint = "PRIMARY KEY" - if column.column_type == "enum": values = ", ".join(f"'{x}'" for x in column.values) column_constraint = f" CHECK({column.name} IN ({values}))" diff --git a/src/masoniteorm/schema/platforms/SQLitePlatform.py b/src/masoniteorm/schema/platforms/SQLitePlatform.py index f52ad14b..d0df9ac7 100644 --- a/src/masoniteorm/schema/platforms/SQLitePlatform.py +++ b/src/masoniteorm/schema/platforms/SQLitePlatform.py @@ -1,3 +1,4 @@ +from ...exceptions import QueryException from ...schema import Schema from ..Table import Table from .Platform import Platform @@ -20,15 +21,15 @@ class SQLitePlatform(Platform): "integer": "INTEGER", "big_integer": "BIGINT", "tiny_integer": "TINYINT", - "big_increments": "BIGINT", "small_integer": "SMALLINT", "medium_integer": "MEDIUMINT", - "integer_unsigned": "INT UNSIGNED", - "big_integer_unsigned": "BIGINT UNSIGNED", - "tiny_integer_unsigned": "TINYINT UNSIGNED", - "small_integer_unsigned": "SMALLINT UNSIGNED", - "medium_integer_unsigned": "MEDIUMINT UNSIGNED", - "increments": "INTEGER", + # Sqlite database does not implement unsigned types + # So the below types are the same as the normal ones + "integer_unsigned": "INT", + "big_integer_unsigned": "BIGINT", + "tiny_integer_unsigned": "TINYINT", + "small_integer_unsigned": "SMALLINT", + "medium_integer_unsigned": "MEDIUMINT", "uuid": "CHAR", "binary": "LONGBLOB", "boolean": "BOOLEAN", @@ -51,8 +52,19 @@ class SQLitePlatform(Platform): "date": "DATE", "year": "VARCHAR", "datetime": "DATETIME", - "tiny_increments": "TINYINT AUTO_INCREMENT", - "unsigned": "INT UNSIGNED", + "unsigned": "INT", + "tiny_increments": "TINYINT", + "increments": "INTEGER", + "big_increments": "BIGINT", + "tiny_increments_primary": "TINYINT PRIMARY KEY AUTOINCREMENT", + "increments_primary": "INTEGER PRIMARY KEY AUTOINCREMENT", + "big_increments_primary": "BIGINT PRIMARY KEY AUTOINCREMENT", + } + + primary_key_type_check = { + "tiny_increments": "tiny_increments() not supported on non-primary key columns. For a primary key use '.tiny_increments('{}').primary()'", + "increments": "increments() not supported on non-primary key columns. For a primary key use '.increments('{}').primary()'", + "big_increments": "big_increments() not supported on non-primary key columns. For a primary key use '.big_increments('{}').primary()'", } premapped_defaults = { @@ -107,7 +119,19 @@ def compile_create_sql(self, table, if_not_exists=False): def columnize(self, columns): sql = [] + + # check for unsupported types for name, column in columns.items(): + constraint = "" + if column.column_type in self.primary_key_type_check: + if not column.primary: + msg = self.primary_key_type_check[ + column.column_type + ].format(column.name) + raise QueryException(msg) + + constraint = "PRIMARY KEY AUTOINCREMENT" + if column.length: length = self.create_column_length(column.column_type).format( length=column.length @@ -132,11 +156,7 @@ def columnize(self, columns): else: default = "" - constraint = "" column_constraint = "" - if column.primary: - constraint = "PRIMARY KEY" - if column.column_type == "enum": values = ", ".join(f"'{x}'" for x in column.values) column_constraint = f" CHECK({column.name} IN ({values}))" @@ -148,12 +168,6 @@ def columnize(self, columns): data_type=self.type_map.get(column.column_type, ""), column_constraint=column_constraint, length=length, - signed=( - " " + self.signed.get(column._signed) - if column.column_type not in self.types_without_signs - and column._signed - else "" - ), constraint=constraint, nullable=self.premapped_nulls.get(column.is_null) or "", default=default, @@ -203,13 +217,6 @@ def compile_alter_sql(self, diff): column_constraint=column_constraint, nullable="NULL" if column.is_null else "NOT NULL", default=default, - signed=( - " " + self.signed.get(column._signed) - if column.column_type - not in self.types_without_signs - and column._signed - else "" - ), constraint=constraint, ) .strip() @@ -325,7 +332,7 @@ def get_column_string(self): return '"{column}"' def add_column_string(self): - return "ALTER TABLE {table} ADD COLUMN {name} {data_type}{column_constraint}{signed} {nullable}{default}{constraint}" + return "ALTER TABLE {table} ADD COLUMN {name} {data_type}{column_constraint} {nullable}{default}{constraint}" def create_column_length(self, column_type): if column_type in self.types_without_lengths: @@ -333,7 +340,7 @@ def create_column_length(self, column_type): return "({length})" def columnize_string(self): - return "{name} {data_type}{length}{column_constraint}{signed} {nullable}{default} {constraint}" + return "{name} {data_type}{length}{column_constraint} {nullable}{default} {constraint}" def get_unique_constraint_string(self): return "UNIQUE({columns})" diff --git a/tests/migrations/test_migrations_table.py b/tests/migrations/test_migrations_table.py new file mode 100644 index 00000000..761ff95b --- /dev/null +++ b/tests/migrations/test_migrations_table.py @@ -0,0 +1,29 @@ +import unittest + +from src.masoniteorm.connections import ConnectionFactory +from src.masoniteorm.migrations import Migration +from src.masoniteorm.schema import Schema +from src.masoniteorm.schema.platforms import SQLitePlatform +from tests.integrations.config.database import DB + + +class TestMigrationsTable(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.connection = ConnectionFactory().make("sqlite") + cls.schema = Schema( + connection="dev", + connection_class=cls.connection, + connection_details=DB.get_connection_details(), + platform=SQLitePlatform, + ) + + cls.schema.drop_table_if_exists("migrations") + + def test_can_create_migrations_table(self): + self.assertFalse(self.schema.has_table("migrations")) + + migration = Migration(connection="dev") + migration.create_table_if_not_exists() + + self.assertTrue(self.schema.has_table("migrations")) diff --git a/tests/mssql/schema/test_mssql_schema_builder.py b/tests/mssql/schema/test_mssql_schema_builder.py index 4205bc7e..7ab18e66 100644 --- a/tests/mssql/schema/test_mssql_schema_builder.py +++ b/tests/mssql/schema/test_mssql_schema_builder.py @@ -1,9 +1,9 @@ import unittest -from tests.integrations.config.database import DATABASES from src.masoniteorm.connections import MSSQLConnection from src.masoniteorm.schema import Schema from src.masoniteorm.schema.platforms import MSSQLPlatform +from tests.integrations.config.database import DATABASES class TestMSSQLSchemaBuilder(unittest.TestCase): @@ -26,7 +26,9 @@ def test_can_add_columns(self): self.assertEqual(len(blueprint.table.added_columns), 2) self.assertEqual( blueprint.to_sql(), - ["CREATE TABLE [users] ([name] VARCHAR(255) NOT NULL, [age] INT NOT NULL)"], + [ + "CREATE TABLE [users] ([name] VARCHAR(255) NOT NULL, [age] INT NOT NULL)" + ], ) def test_can_add_tiny_text(self): @@ -69,7 +71,10 @@ def test_can_have_float_type(self): self.assertEqual( blueprint.to_sql(), - ["""CREATE TABLE [users] (""" """[amount] FLOAT(19, 4) NOT NULL)"""], + [ + """CREATE TABLE [users] (""" + """[amount] FLOAT(19, 4) NOT NULL)""" + ], ) def test_can_have_unsigned_columns(self): @@ -134,30 +139,32 @@ def test_can_add_columns_with_add_foreign_constaint(self): def test_can_advanced_table_creation(self): with self.schema.create("users") as blueprint: - blueprint.increments("id") + blueprint.increments("id").primary() blueprint.string("name") blueprint.string("email").unique() blueprint.string("password") blueprint.integer("admin").default(0) blueprint.string("remember_token").nullable() blueprint.timestamp("verified_at").nullable() - blueprint.timestamp("registered_at").default_raw("CURRENT_TIMESTAMP") + blueprint.timestamp("registered_at").default_raw( + "CURRENT_TIMESTAMP" + ) blueprint.timestamps() self.assertEqual(len(blueprint.table.added_columns), 10) self.assertEqual( blueprint.to_sql(), [ - "CREATE TABLE [users] ([id] INT IDENTITY NOT NULL, [name] VARCHAR(255) NOT NULL, [email] VARCHAR(255) NOT NULL, " + "CREATE TABLE [users] ([id] INT PRIMARY KEY IDENTITY NOT NULL, [name] VARCHAR(255) NOT NULL, [email] VARCHAR(255) NOT NULL, " "[password] VARCHAR(255) NOT NULL, [admin] INT NOT NULL DEFAULT 0, [remember_token] VARCHAR(255) NULL, " "[verified_at] DATETIME NULL, [registered_at] DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, [created_at] DATETIME NULL DEFAULT CURRENT_TIMESTAMP, " - "[updated_at] DATETIME NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT users_id_primary PRIMARY KEY (id), CONSTRAINT users_email_unique UNIQUE (email))" + "[updated_at] DATETIME NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT users_email_unique UNIQUE (email))" ], ) def test_can_advanced_table_creation2(self): with self.schema.create("users") as blueprint: - blueprint.increments("id") + blueprint.increments("id").primary() blueprint.enum("gender", ["male", "female"]) blueprint.string("name") blueprint.string("duration") @@ -169,9 +176,9 @@ def test_can_advanced_table_creation2(self): blueprint.string("thumbnail").nullable() blueprint.integer("premium") blueprint.integer("author_id").unsigned().nullable() - blueprint.foreign("author_id").references("id").on("users").on_delete( - "CASCADE" - ) + blueprint.foreign("author_id").references("id").on( + "users" + ).on_delete("CASCADE") blueprint.text("description") blueprint.timestamps() @@ -180,11 +187,11 @@ def test_can_advanced_table_creation2(self): blueprint.to_sql(), ( [ - "CREATE TABLE [users] ([id] INT IDENTITY NOT NULL, [gender] VARCHAR(255) NOT NULL CHECK([gender] IN ('male', 'female')), [name] VARCHAR(255) NOT NULL, [duration] VARCHAR(255) NOT NULL, " + "CREATE TABLE [users] ([id] INT PRIMARY KEY IDENTITY NOT NULL, [gender] VARCHAR(255) NOT NULL CHECK([gender] IN ('male', 'female')), [name] VARCHAR(255) NOT NULL, [duration] VARCHAR(255) NOT NULL, " "[url] VARCHAR(255) NOT NULL, [last_address] VARCHAR(255) NULL, [route_origin] VARCHAR(255) NULL, [mac_address] VARCHAR(255) NULL, [published_at] DATETIME NOT NULL, [thumbnail] VARCHAR(255) NULL, [premium] INT NOT NULL, " "[author_id] INT NULL, [description] TEXT NOT NULL, [created_at] DATETIME NULL DEFAULT CURRENT_TIMESTAMP, " "[updated_at] DATETIME NULL DEFAULT CURRENT_TIMESTAMP, " - "CONSTRAINT users_id_primary PRIMARY KEY (id), CONSTRAINT users_author_id_foreign FOREIGN KEY ([author_id]) REFERENCES [users]([id]) ON DELETE CASCADE)" + "CONSTRAINT users_author_id_foreign FOREIGN KEY ([author_id]) REFERENCES [users]([id]) ON DELETE CASCADE)" ] ), ) @@ -192,9 +199,9 @@ def test_can_advanced_table_creation2(self): def test_can_add_columns_with_foreign_key_constraint_name(self): with self.schema.create("users") as blueprint: blueprint.integer("profile_id") - blueprint.foreign("profile_id", name="profile_foreign").references("id").on( - "profiles" - ) + blueprint.foreign("profile_id", name="profile_foreign").references( + "id" + ).on("profiles") self.assertEqual(len(blueprint.table.added_columns), 1) self.assertEqual( @@ -315,7 +322,9 @@ def test_can_add_enum(self): def test_can_change_column_enum(self): with self.schema.table("users") as blueprint: - blueprint.enum("status", ["active", "inactive"]).default("active").change() + blueprint.enum("status", ["active", "inactive"]).default( + "active" + ).change() self.assertEqual(len(blueprint.table.changed_columns), 1) self.assertEqual( diff --git a/tests/mysql/schema/test_mysql_schema_builder.py b/tests/mysql/schema/test_mysql_schema_builder.py index b3aefc03..69fabbe5 100644 --- a/tests/mysql/schema/test_mysql_schema_builder.py +++ b/tests/mysql/schema/test_mysql_schema_builder.py @@ -2,11 +2,9 @@ import unittest from src.masoniteorm import Model -from tests.integrations.config.database import DATABASES from src.masoniteorm.connections import MySQLConnection from src.masoniteorm.schema import Schema from src.masoniteorm.schema.platforms import MySQLPlatform - from tests.integrations.config.database import DATABASES @@ -56,7 +54,9 @@ def test_can_add_unsigned_decimal(self): self.assertEqual(len(blueprint.table.added_columns), 1) self.assertEqual( blueprint.to_sql(), - ["CREATE TABLE `users` (`amount` DECIMAL(19, 4) UNSIGNED NOT NULL)"], + [ + "CREATE TABLE `users` (`amount` DECIMAL(19, 4) UNSIGNED NOT NULL)" + ], ) def test_can_create_table_if_not_exists(self): @@ -119,7 +119,9 @@ def test_can_add_columns_with_foreign_key_constaint(self): blueprint.integer("profile_id") blueprint.foreign("profile_id").references("id").on("profiles") blueprint.foreign_id("post_id").references("id").on("posts") - blueprint.foreign_id_for(Discussion).references("id").on("discussions") + blueprint.foreign_id_for(Discussion).references("id").on( + "discussions" + ) self.assertEqual(len(blueprint.table.added_columns), 3) self.assertEqual( @@ -176,11 +178,12 @@ def test_can_advanced_table_creation(self): blueprint.to_sql(), [ "CREATE TABLE `users` (`id` INT UNSIGNED AUTO_INCREMENT NOT NULL, " - "`id2` BIGINT UNSIGNED AUTO_INCREMENT NOT NULL, " + "`id2` BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT NOT NULL, " "`name` VARCHAR(255) NOT NULL, `active` TINYINT(1) NOT NULL, `email` VARCHAR(255) NOT NULL, `gender` ENUM('male', 'female') NOT NULL, " "`password` VARCHAR(255) NOT NULL, `money` DECIMAL(17, 6) NOT NULL, " "`admin` INT(11) NOT NULL DEFAULT 0, `option` VARCHAR(255) NOT NULL DEFAULT 'ADMIN', `remember_token` VARCHAR(255) NULL, `verified_at` TIMESTAMP NULL, " - "`created_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT users_id_primary PRIMARY KEY (id), CONSTRAINT users_id2_primary PRIMARY KEY (id2), CONSTRAINT users_email_unique UNIQUE (email))" + "`created_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP, " + "CONSTRAINT users_email_unique UNIQUE (email))" ], ) @@ -201,7 +204,7 @@ def test_can_add_primary_constraint_without_column_name(self): def test_can_advanced_table_creation2(self): with self.schema.create("users") as blueprint: - blueprint.big_increments("id") + blueprint.big_increments("id").primary() blueprint.string("name") blueprint.string("duration") blueprint.string("url") @@ -212,9 +215,9 @@ def test_can_advanced_table_creation2(self): blueprint.string("thumbnail").nullable() blueprint.integer("premium") blueprint.integer("author_id").unsigned().nullable() - blueprint.foreign("author_id").references("id").on("users").on_delete( - "CASCADE" - ) + blueprint.foreign("author_id").references("id").on( + "users" + ).on_delete("CASCADE") blueprint.text("description") blueprint.timestamps() @@ -222,20 +225,20 @@ def test_can_advanced_table_creation2(self): self.assertEqual( blueprint.to_sql(), [ - "CREATE TABLE `users` (`id` BIGINT UNSIGNED AUTO_INCREMENT NOT NULL, `name` VARCHAR(255) NOT NULL, " + "CREATE TABLE `users` (`id` BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT NOT NULL, `name` VARCHAR(255) NOT NULL, " "`duration` VARCHAR(255) NOT NULL, `url` VARCHAR(255) NOT NULL, `last_address` VARCHAR(255) NULL, `route_origin` VARCHAR(255) NULL, `mac_address` VARCHAR(255) NULL, " "`published_at` DATETIME NOT NULL, `thumbnail` VARCHAR(255) NULL, " "`premium` INT(11) NOT NULL, `author_id` INT(11) UNSIGNED NULL, `description` TEXT NOT NULL, `created_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP, " - "`updated_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT users_id_primary PRIMARY KEY (id), CONSTRAINT users_author_id_foreign FOREIGN KEY (`author_id`) REFERENCES `users`(`id`) ON DELETE CASCADE)" + "`updated_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT users_author_id_foreign FOREIGN KEY (`author_id`) REFERENCES `users`(`id`) ON DELETE CASCADE)" ], ) def test_can_add_columns_with_foreign_key_constraint_name(self): with self.schema.create("users") as blueprint: blueprint.integer("profile_id") - blueprint.foreign("profile_id", name="profile_foreign").references("id").on( - "profiles" - ) + blueprint.foreign("profile_id", name="profile_foreign").references( + "id" + ).on("profiles") self.assertEqual(len(blueprint.table.added_columns), 1) self.assertEqual( @@ -316,7 +319,10 @@ def test_can_have_default_blank_string(self): self.assertEqual( blueprint.to_sql(), - ["CREATE TABLE `users` (" "`profile_id` VARCHAR(255) NOT NULL DEFAULT '')"], + [ + "CREATE TABLE `users` (" + "`profile_id` VARCHAR(255) NOT NULL DEFAULT '')" + ], ) def test_can_have_float_type(self): diff --git a/tests/postgres/schema/test_postgres_schema_builder.py b/tests/postgres/schema/test_postgres_schema_builder.py index 9e04b3dd..4eb13eca 100644 --- a/tests/postgres/schema/test_postgres_schema_builder.py +++ b/tests/postgres/schema/test_postgres_schema_builder.py @@ -1,9 +1,9 @@ import unittest -from tests.integrations.config.database import DATABASES from src.masoniteorm.connections import PostgresConnection from src.masoniteorm.schema import Schema from src.masoniteorm.schema.platforms import PostgresPlatform +from tests.integrations.config.database import DATABASES class TestPostgresSchemaBuilder(unittest.TestCase): @@ -37,7 +37,8 @@ def test_can_add_tiny_text(self): self.assertEqual(len(blueprint.table.added_columns), 1) self.assertEqual( - blueprint.to_sql(), ['CREATE TABLE "users" ("description" TEXT NOT NULL)'] + blueprint.to_sql(), + ['CREATE TABLE "users" ("description" TEXT NOT NULL)'], ) def test_can_add_unsigned_decimal(self): @@ -138,7 +139,8 @@ def test_can_add_columns_with_long_text(self): self.assertEqual(len(blueprint.table.added_columns), 1) self.assertEqual( - blueprint.to_sql(), ['CREATE TABLE "users" ("description" TEXT NOT NULL)'] + blueprint.to_sql(), + ['CREATE TABLE "users" ("description" TEXT NOT NULL)'], ) def test_can_have_unsigned_columns(self): @@ -180,7 +182,7 @@ def test_can_add_columns_with_foreign_key_constaint(self): def test_can_advanced_table_creation(self): with self.schema.create("users") as blueprint: - blueprint.increments("id") + blueprint.increments("id").primary() blueprint.string("name") blueprint.string("email").unique() blueprint.string("password") @@ -193,17 +195,17 @@ def test_can_advanced_table_creation(self): self.assertEqual( blueprint.to_sql(), [ - 'CREATE TABLE "users" ("id" SERIAL UNIQUE NOT NULL, "name" VARCHAR(255) NOT NULL, ' + 'CREATE TABLE "users" ("id" SERIAL PRIMARY KEY NOT NULL, "name" VARCHAR(255) NOT NULL, ' '"email" VARCHAR(255) NOT NULL, "password" VARCHAR(255) NOT NULL, "admin" INTEGER NOT NULL DEFAULT 0, ' '"remember_token" VARCHAR(255) NULL, "verified_at" TIMESTAMP NULL, ' '"created_at" TIMESTAMPTZ NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMPTZ NULL DEFAULT CURRENT_TIMESTAMP, ' - "CONSTRAINT users_id_primary PRIMARY KEY (id), CONSTRAINT users_email_unique UNIQUE (email))" + "CONSTRAINT users_email_unique UNIQUE (email))" ], ) def test_can_advanced_table_creation2(self): with self.schema.create("users") as blueprint: - blueprint.big_increments("id") + blueprint.big_increments("id").primary() blueprint.string("name") blueprint.enum("gender", ["male", "female"]) blueprint.string("duration") @@ -219,9 +221,9 @@ def test_can_advanced_table_creation2(self): blueprint.integer("premium") blueprint.double("amount").default(0.0) blueprint.integer("author_id").unsigned().nullable() - blueprint.foreign("author_id").references("id").on("authors").on_delete( - "CASCADE" - ) + blueprint.foreign("author_id").references("id").on( + "authors" + ).on_delete("CASCADE") blueprint.text("description") blueprint.timestamps() @@ -230,12 +232,12 @@ def test_can_advanced_table_creation2(self): blueprint.to_sql(), ( [ - """CREATE TABLE "users" ("id" BIGSERIAL UNIQUE NOT NULL, "name" VARCHAR(255) NOT NULL, "gender" VARCHAR(255) CHECK(gender IN ('male', 'female')) NOT NULL, """ + """CREATE TABLE "users" ("id" BIGSERIAL PRIMARY KEY NOT NULL, "name" VARCHAR(255) NOT NULL, "gender" VARCHAR(255) CHECK(gender IN ('male', 'female')) NOT NULL, """ """"duration" VARCHAR(255) NOT NULL, "money" DECIMAL(17, 6) NOT NULL, "url" VARCHAR(255) NOT NULL, "option" VARCHAR(255) NOT NULL DEFAULT 'ADMIN', "payload" JSONB NOT NULL, "last_address" INET NULL, """ '"route_origin" CIDR NULL, "mac_address" MACADDR NULL, "published_at" TIMESTAMPTZ NOT NULL, "thumbnail" VARCHAR(255) NULL, "premium" INTEGER NOT NULL, "amount" DOUBLE PRECISION NOT NULL DEFAULT 0.0, ' '"author_id" INTEGER NULL, "description" TEXT NOT NULL, "created_at" TIMESTAMPTZ NULL DEFAULT CURRENT_TIMESTAMP, ' '"updated_at" TIMESTAMPTZ NULL DEFAULT CURRENT_TIMESTAMP, ' - 'CONSTRAINT users_id_primary PRIMARY KEY (id), CONSTRAINT users_author_id_foreign FOREIGN KEY ("author_id") REFERENCES "authors"("id") ON DELETE CASCADE)' + 'CONSTRAINT users_author_id_foreign FOREIGN KEY ("author_id") REFERENCES "authors"("id") ON DELETE CASCADE)' ] ), ) @@ -261,9 +263,9 @@ def test_can_add_uuid_column(self): def test_can_add_columns_with_foreign_key_constraint_name(self): with self.schema.create("users") as blueprint: blueprint.integer("profile_id") - blueprint.foreign("profile_id", name="profile_foreign").references("id").on( - "profiles" - ) + blueprint.foreign("profile_id", name="profile_foreign").references( + "id" + ).on("profiles") self.assertEqual(len(blueprint.table.added_columns), 1) self.assertEqual( @@ -344,7 +346,10 @@ def test_can_have_float_type(self): self.assertEqual( blueprint.to_sql(), - ["""CREATE TABLE "users" (""" """\"amount" FLOAT(19, 4) NOT NULL)"""], + [ + """CREATE TABLE "users" (""" + """\"amount" FLOAT(19, 4) NOT NULL)""" + ], ) def test_can_enable_foreign_keys(self): @@ -377,6 +382,7 @@ def test_can_add_enum(self): self.assertEqual( blueprint.to_sql(), [ - 'CREATE TABLE "users" ("status" VARCHAR(255) CHECK(status IN (\'active\', \'inactive\')) NOT NULL ' 'DEFAULT \'active\')' + "CREATE TABLE \"users\" (\"status\" VARCHAR(255) CHECK(status IN ('active', 'inactive')) NOT NULL " + "DEFAULT 'active')" ], ) diff --git a/tests/sqlite/models/test_sqlite_model.py b/tests/sqlite/models/test_sqlite_model.py index 530c6641..b3bbfe44 100644 --- a/tests/sqlite/models/test_sqlite_model.py +++ b/tests/sqlite/models/test_sqlite_model.py @@ -221,19 +221,19 @@ def test_should_return_relation_applying_hidden_attributes(self): schema.drop_table_if_exists(table) with schema.create("users_hidden") as blueprint: - blueprint.increments("id") + blueprint.integer("id").primary() blueprint.string("name") blueprint.integer("token") blueprint.string("password") blueprint.timestamps() with schema.create("groups") as blueprint: - blueprint.increments("id") + blueprint.integer("id").primary() blueprint.string("name") blueprint.timestamps() with schema.create("group_user") as blueprint: - blueprint.increments("id") + blueprint.integer("id").primary() blueprint.unsigned_integer("group_id") blueprint.unsigned_integer("user_id") @@ -251,7 +251,7 @@ def test_should_return_relation_applying_hidden_attributes(self): user = UserHydrateHidden.first() group = Group.first() - group.attach_related("team", user) + group.attach("team", user) serialized = Group.first().serialize() diff --git a/tests/sqlite/schema/test_sqlite_schema_builder.py b/tests/sqlite/schema/test_sqlite_schema_builder.py index 461abd55..83d59990 100644 --- a/tests/sqlite/schema/test_sqlite_schema_builder.py +++ b/tests/sqlite/schema/test_sqlite_schema_builder.py @@ -1,5 +1,6 @@ import unittest +from src.masoniteorm.exceptions import QueryException from src.masoniteorm.schema import Schema from src.masoniteorm.schema.platforms import SQLitePlatform from tests.integrations.config.database import DATABASES @@ -136,7 +137,7 @@ def test_can_use_morphs_for_polymorphism_relationships(self): self.assertEqual(len(blueprint.table.added_columns), 2) sql = [ - 'CREATE TABLE "likes" ("record_id" INTEGER UNSIGNED NOT NULL, "record_type" VARCHAR(255) NOT NULL)', + 'CREATE TABLE "likes" ("record_id" INTEGER NOT NULL, "record_type" VARCHAR(255) NOT NULL)', 'CREATE INDEX likes_record_id_index ON "likes"(record_id)', 'CREATE INDEX likes_record_type_index ON "likes"(record_type)', ] @@ -144,7 +145,7 @@ def test_can_use_morphs_for_polymorphism_relationships(self): def test_can_advanced_table_creation(self): with self.schema.create("users") as blueprint: - blueprint.increments("id") + blueprint.integer("id") blueprint.string("name") blueprint.enum("gender", ["male", "female"]) blueprint.string("email").unique() @@ -163,7 +164,7 @@ def test_can_advanced_table_creation(self): """CREATE TABLE "users" ("id" INTEGER NOT NULL, "name" VARCHAR(255) NOT NULL, "gender" VARCHAR(255) CHECK(gender IN ('male', 'female')) NOT NULL, "email" VARCHAR(255) NOT NULL, """ """"password" VARCHAR(255) NOT NULL, "option" VARCHAR(255) NOT NULL DEFAULT 'ADMIN', "admin" INTEGER NOT NULL DEFAULT 0, "remember_token" VARCHAR(255) NULL, """ '"verified_at" TIMESTAMP NULL, "created_at" DATETIME NULL DEFAULT CURRENT_TIMESTAMP, ' - '"updated_at" DATETIME NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT users_id_primary PRIMARY KEY (id), ' + '"updated_at" DATETIME NULL DEFAULT CURRENT_TIMESTAMP, ' "UNIQUE(email), UNIQUE(email, name))" ], ) @@ -243,9 +244,24 @@ def test_can_have_column_primary_key(self): ], ) + def test_cannot_have_unsupported_types(self): + with self.assertRaises(QueryException): + with self.schema.create("users200") as blueprint: + blueprint.increments("id") + blueprint.to_sql() + + with self.schema.create("users200") as blueprint: + blueprint.tiny_increments("id") + blueprint.to_sql() + + with self.schema.create("users200") as blueprint: + blueprint.big_increments("id") + blueprint.to_sql() + def test_can_advanced_table_creation2(self): with self.schema.create("users") as blueprint: - blueprint.big_increments("id") + blueprint.big_integer("id") + blueprint.primary("id") blueprint.string("name") blueprint.string("duration") blueprint.string("url") @@ -266,14 +282,13 @@ def test_can_advanced_table_creation2(self): blueprint.timestamps() self.assertEqual(len(blueprint.table.added_columns), 17) - self.assertEqual( blueprint.to_sql(), ( [ 'CREATE TABLE "users" ("id" BIGINT NOT NULL, "name" VARCHAR(255) NOT NULL, "duration" VARCHAR(255) NOT NULL, ' '"url" VARCHAR(255) NOT NULL, "payload" JSON NOT NULL, "birth" VARCHAR(4) NOT NULL, "last_address" VARCHAR(255) NULL, "route_origin" VARCHAR(255) NULL, "mac_address" VARCHAR(255) NULL, ' - '"published_at" DATETIME NOT NULL, "wakeup_at" TIME NOT NULL, "thumbnail" VARCHAR(255) NULL, "premium" INTEGER NOT NULL, "author_id" INTEGER UNSIGNED NULL, "description" TEXT NOT NULL, ' + '"published_at" DATETIME NOT NULL, "wakeup_at" TIME NOT NULL, "thumbnail" VARCHAR(255) NULL, "premium" INTEGER NOT NULL, "author_id" INTEGER NULL, "description" TEXT NOT NULL, ' '"created_at" DATETIME NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" DATETIME NULL DEFAULT CURRENT_TIMESTAMP, ' 'CONSTRAINT users_id_primary PRIMARY KEY (id), CONSTRAINT users_author_id_foreign FOREIGN KEY ("author_id") REFERENCES "users"("id") ON DELETE SET NULL)' ] @@ -327,11 +342,11 @@ def test_can_have_unsigned_columns(self): blueprint.to_sql(), [ """CREATE TABLE "users" (""" - """"profile_id" INTEGER UNSIGNED NOT NULL, """ - """"big_profile_id" BIGINT UNSIGNED NOT NULL, """ - """"tiny_profile_id" TINYINT UNSIGNED NOT NULL, """ - """"small_profile_id" SMALLINT UNSIGNED NOT NULL, """ - """"medium_profile_id" MEDIUMINT UNSIGNED NOT NULL)""" + """"profile_id" INTEGER NOT NULL, """ + """"big_profile_id" BIGINT NOT NULL, """ + """"tiny_profile_id" TINYINT NOT NULL, """ + """"small_profile_id" SMALLINT NOT NULL, """ + """"medium_profile_id" MEDIUMINT NOT NULL)""" ], ) diff --git a/tests/sqlite/schema/test_sqlite_schema_builder_alter.py b/tests/sqlite/schema/test_sqlite_schema_builder_alter.py index 677b1445..84472804 100644 --- a/tests/sqlite/schema/test_sqlite_schema_builder_alter.py +++ b/tests/sqlite/schema/test_sqlite_schema_builder_alter.py @@ -9,8 +9,9 @@ class TestSQLiteSchemaBuilderAlter(unittest.TestCase): maxDiff = None - def setUp(self): - self.schema = Schema( + @classmethod + def setUpClass(cls): + cls.schema = Schema( connection="dev", connection_details=DATABASES, platform=SQLitePlatform, @@ -142,17 +143,6 @@ def test_timestamp_alter_add_nullable_column(self): self.assertEqual(blueprint.to_sql(), sql) - def test_alter_drop_on_table_schema_table(self): - schema = Schema(connection="dev", connection_details=DATABASES).on( - "dev" - ) - - with schema.table("table_schema") as blueprint: - blueprint.drop_column("name") - - with schema.table("table_schema") as blueprint: - blueprint.string("name").nullable() - def test_alter_add_primary(self): with self.schema.table("users") as blueprint: blueprint.primary("playlist_id") @@ -177,10 +167,10 @@ def test_alter_add_column_and_foreign_key(self): blueprint.table.from_table = table sql = [ - 'ALTER TABLE "users" ADD COLUMN "playlist_id" INTEGER UNSIGNED NULL REFERENCES "playlists"("id")', + 'ALTER TABLE "users" ADD COLUMN "playlist_id" INTEGER NULL REFERENCES "playlists"("id")', "CREATE TEMPORARY TABLE __temp__users AS SELECT age, email FROM users", 'DROP TABLE "users"', - 'CREATE TABLE "users" ("age" VARCHAR NOT NULL, "email" VARCHAR NOT NULL, "playlist_id" INTEGER UNSIGNED NULL, ' + 'CREATE TABLE "users" ("age" VARCHAR NOT NULL, "email" VARCHAR NOT NULL, "playlist_id" INTEGER NULL, ' 'CONSTRAINT users_playlist_id_foreign FOREIGN KEY ("playlist_id") REFERENCES "playlists"("id") ON DELETE CASCADE ON UPDATE SET NULL)', 'INSERT INTO "users" ("age", "email") SELECT age, email FROM __temp__users', "DROP TABLE __temp__users",