diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/relationships/test_sqlite_has_one_through_relationship.py b/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/relationships/test_sqlite_has_one_through_relationship.py deleted file mode 100644 index 37b11e92..00000000 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/relationships/test_sqlite_has_one_through_relationship.py +++ /dev/null @@ -1,162 +0,0 @@ -import pytest_asyncio - -from fastapi_startkit.masoniteorm.connections.sqlite_connection import SQLiteConnection -from fastapi_startkit.masoniteorm.models import Model -from fastapi_startkit.masoniteorm.relationships import HasOneThrough -from fastapi_startkit.masoniteorm.schema import Schema -from fastapi_startkit.masoniteorm.schema.platforms import SQLitePlatform - - -class Port(Model): - __table__ = "ports" - __connection__ = "dev" - - -class Country(Model): - __table__ = "countries" - __connection__ = "dev" - - -class IncomingShipment(Model): - __table__ = "incoming_shipments" - __connection__ = "dev" - - from_country: "Country" = HasOneThrough( - ["Country", "Port"], - "from_port_id", # FK on IncomingShipment → Port - "port_country_id", # FK on Port → Country - "port_id", # PK on Port - "country_id", # PK on Country - ) - - -class TestHasOneThroughRelationship: - @pytest_asyncio.fixture(autouse=True) - async def setup(self): - # Reset shared engine cache so each test class gets a fresh in-memory DB. - SQLiteConnection._shared_engines.clear() - - self.schema = Schema( - connection="dev", - platform=SQLitePlatform, - config_path="fastapi_startkit/masoniteorm/tests/integrations/config/database", - ).on("dev") - - async with await self.schema.create_table_if_not_exists( - "incoming_shipments" - ) as table: - table.integer("shipment_id").primary() - table.string("name") - table.integer("from_port_id") - - async with await self.schema.create_table_if_not_exists("ports") as table: - table.integer("port_id").primary() - table.string("name") - table.integer("port_country_id") - - async with await self.schema.create_table_if_not_exists("countries") as table: - table.integer("country_id").primary() - table.string("name") - - await ( - Country() - .get_builder() - .bulk_create( - [ - {"country_id": 10, "name": "Australia"}, - {"country_id": 20, "name": "USA"}, - {"country_id": 30, "name": "Canada"}, - {"country_id": 40, "name": "United Kingdom"}, - ] - ) - ) - - await ( - Port() - .get_builder() - .bulk_create( - [ - {"port_id": 100, "name": "Melbourne", "port_country_id": 10}, - {"port_id": 200, "name": "Darwin", "port_country_id": 10}, - {"port_id": 300, "name": "South Louisiana", "port_country_id": 20}, - {"port_id": 400, "name": "Houston", "port_country_id": 20}, - {"port_id": 500, "name": "Montreal", "port_country_id": 30}, - {"port_id": 600, "name": "Vancouver", "port_country_id": 30}, - {"port_id": 700, "name": "Southampton", "port_country_id": 40}, - {"port_id": 800, "name": "London Gateway", "port_country_id": 40}, - ] - ) - ) - - await ( - IncomingShipment() - .get_builder() - .bulk_create( - [ - {"name": "Bread", "from_port_id": 300}, - {"name": "Milk", "from_port_id": 100}, - {"name": "Tractor Parts", "from_port_id": 100}, - {"name": "Fridges", "from_port_id": 700}, - {"name": "Wheat", "from_port_id": 600}, - {"name": "Kettles", "from_port_id": 400}, - {"name": "Bread", "from_port_id": 700}, - ] - ) - ) - - yield - - await self.schema.drop_table_if_exists("incoming_shipments") - await self.schema.drop_table_if_exists("ports") - await self.schema.drop_table_if_exists("countries") - SQLiteConnection._shared_engines.clear() - - async def test_has_one_through_can_eager_load(self): - shipments = await ( - IncomingShipment.where("name", "Bread").with_("from_country").get() - ) - assert shipments.count() == 2 - - shipment1 = shipments.shift() - assert isinstance(shipment1.from_country, Country) - assert shipment1.from_country.country_id == 20 - - shipment2 = shipments.shift() - assert isinstance(shipment2.from_country, Country) - assert shipment2.from_country.country_id == 40 - - # check .first() and .get() produce the same result - single = await ( - IncomingShipment.where("name", "Tractor Parts") - .with_("from_country") - .first() - ) - single_get = await ( - IncomingShipment.where("name", "Tractor Parts").with_("from_country").get() - ) - assert single.from_country.country_id == 10 - assert single_get.count() == 1 - assert ( - single.from_country.country_id == single_get.first().from_country.country_id - ) - - async def test_has_one_through_eager_load_can_be_empty(self): - shipments = await ( - IncomingShipment.where("name", "Bread") - .where_has("from_country", lambda query: query.where("name", "Uruguay")) - .with_("from_country") - .get() - ) - assert shipments.count() == 0 - - async def test_has_one_through_can_get_related(self): - shipment = await IncomingShipment.where("name", "Milk").first() - country = await shipment.from_country - assert isinstance(country, Country) - assert country.country_id == 10 - - async def test_has_one_through_has_query(self): - shipments = await IncomingShipment.where_has( - "from_country", lambda query: query.where("name", "USA") - ).get() - assert shipments.count() == 2 diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/schema/test_sqlite_schema_builder_alter.py b/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/schema/test_sqlite_schema_builder_alter.py deleted file mode 100644 index 45179ced..00000000 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/schema/test_sqlite_schema_builder_alter.py +++ /dev/null @@ -1,240 +0,0 @@ -import unittest - -from src.masoniteorm.schema import Schema -from src.masoniteorm.schema.platforms import SQLitePlatform -from src.masoniteorm.schema.Table import Table -from tests.integrations.config.database import DATABASES - - -class TestSQLiteSchemaBuilderAlter(unittest.TestCase): - maxDiff = None - - def setUp(self): - self.schema = Schema( - connection="dev", - connection_details=DATABASES, - platform=SQLitePlatform, - dry=True, - ).on("dev") - - def test_can_add_columns(self): - with self.schema.table("users") as blueprint: - blueprint.string("name") - blueprint.string("external_type").default("external") - blueprint.integer("age") - - self.assertEqual(len(blueprint.table.added_columns), 3) - - sql = [ - 'ALTER TABLE "users" ADD COLUMN "name" VARCHAR NOT NULL', - """ALTER TABLE "users" ADD COLUMN "external_type" VARCHAR NOT NULL DEFAULT 'external'""", - 'ALTER TABLE "users" ADD COLUMN "age" INTEGER NOT NULL', - ] - - self.assertEqual(blueprint.to_sql(), sql) - - def test_can_add_constraints(self): - with self.schema.table("users") as blueprint: - blueprint.unique("name", name="table_unique") - - self.assertEqual(len(blueprint.table.added_columns), 0) - - sql = ['CREATE UNIQUE INDEX table_unique ON "users"(name)'] - - self.assertEqual(blueprint.to_sql(), sql) - - def test_alter_rename(self): - with self.schema.table("users") as blueprint: - blueprint.rename("post", "comment", "integer") - - table = Table("users") - table.add_column("post", "integer") - blueprint.table.from_table = table - - sql = [ - "CREATE TEMPORARY TABLE __temp__users AS SELECT post FROM users", - 'DROP TABLE "users"', - 'CREATE TABLE "users" ("comment" INTEGER NOT NULL)', - 'INSERT INTO "users" ("comment") SELECT post FROM __temp__users', - "DROP TABLE __temp__users", - ] - - self.assertEqual(blueprint.to_sql(), sql) - - def test_alter_drop(self): - with self.schema.table("users") as blueprint: - blueprint.drop_column("post") - - table = Table("users") - table.add_column("post", "string") - table.add_column("name", "string") - table.add_column("email", "string") - blueprint.table.from_table = table - - sql = [ - "CREATE TEMPORARY TABLE __temp__users AS SELECT name, email FROM users", - 'DROP TABLE "users"', - 'CREATE TABLE "users" ("name" VARCHAR NOT NULL, "email" VARCHAR NOT NULL)', - 'INSERT INTO "users" ("name", "email") SELECT name, email FROM __temp__users', - "DROP TABLE __temp__users", - ] - - self.assertEqual(blueprint.to_sql(), sql) - - def test_change(self): - with self.schema.table("users") as blueprint: - blueprint.integer("age").change() - blueprint.string("name") - - self.assertEqual(len(blueprint.table.added_columns), 1) - self.assertEqual(len(blueprint.table.changed_columns), 1) - table = Table("users") - table.add_column("age", "string") - - blueprint.table.from_table = table - - sql = [ - 'ALTER TABLE "users" ADD COLUMN "name" VARCHAR NOT NULL', - "CREATE TEMPORARY TABLE __temp__users AS SELECT age FROM users", - 'DROP TABLE "users"', - 'CREATE TABLE "users" ("age" INTEGER NOT NULL, "name" VARCHAR(255) NOT NULL)', - 'INSERT INTO "users" ("age") SELECT age FROM __temp__users', - "DROP TABLE __temp__users", - ] - - self.assertEqual(blueprint.to_sql(), sql) - - def test_drop_add_and_change(self): - with self.schema.table("users") as blueprint: - blueprint.integer("age").change() - blueprint.string("name") - blueprint.drop_column("email") - - self.assertEqual(len(blueprint.table.added_columns), 1) - self.assertEqual(len(blueprint.table.changed_columns), 1) - table = Table("users") - table.add_column("age", "string") - table.add_column("email", "string") - - blueprint.table.from_table = table - - # sql = [ - # 'ALTER TABLE "users" ADD COLUMN "name" VARCHAR', - # "CREATE TEMPORARY TABLE __temp__users AS SELECT age FROM users", - # 'DROP TABLE "users"', - # 'CREATE TABLE "users" ("age" INTEGER NOT NULL, "name" VARCHAR(255) NOT NULL)', - # 'INSERT INTO "users" ("age") SELECT age FROM __temp__users', - # "DROP TABLE __temp__users", - # ] - - def test_timestamp_alter_add_nullable_column(self): - with self.schema.table("users") as blueprint: - blueprint.timestamp("due_date").nullable() - - self.assertEqual(len(blueprint.table.added_columns), 1) - - table = Table("users") - table.add_column("age", "string") - - blueprint.table.from_table = table - - sql = ['ALTER TABLE "users" ADD COLUMN "due_date" TIMESTAMP NULL'] - - 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") - - sql = [ - 'ALTER TABLE "users" ADD CONSTRAINT users_playlist_id_primary PRIMARY KEY (playlist_id)' - ] - - self.assertEqual(blueprint.to_sql(), sql) - - def test_alter_add_column_and_foreign_key(self): - with self.schema.table("users") as blueprint: - blueprint.unsigned_integer("playlist_id").nullable() - blueprint.foreign("playlist_id").references("id").on("playlists").on_delete( - "cascade" - ).on_update("SET NULL") - - table = Table("users") - table.add_column("age", "string") - table.add_column("email", "string") - - blueprint.table.from_table = table - - sql = [ - 'ALTER TABLE "users" ADD COLUMN "playlist_id" INTEGER UNSIGNED 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, ' - '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", - ] - - self.assertEqual(blueprint.to_sql(), sql) - - def test_alter_add_foreign_key_only(self): - with self.schema.table("users") as blueprint: - blueprint.foreign("playlist_id").references("id").on("playlists").on_delete( - "cascade" - ).on_update("set null") - - table = Table("users") - table.add_column("age", "string") - table.add_column("email", "string") - - blueprint.table.from_table = table - - sql = [ - "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, ' - '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", - ] - - self.assertEqual(blueprint.to_sql(), sql) - - def test_can_add_column_enum(self): - with self.schema.table("users") as blueprint: - blueprint.enum("status", ["active", "inactive"]).default("active") - - self.assertEqual(len(blueprint.table.added_columns), 1) - - sql = [ - "ALTER TABLE \"users\" ADD COLUMN \"status\" VARCHAR CHECK('status' IN('active', 'inactive')) NOT NULL DEFAULT 'active'" - ] - - self.assertEqual(blueprint.to_sql(), sql) - - def test_can_change_column_enum(self): - with self.schema.table("users") as blueprint: - blueprint.enum("status", ["active", "inactive"]).default("active").change() - - blueprint.table.from_table = Table("users") - - self.assertEqual(len(blueprint.table.changed_columns), 1) - - sql = [ - "CREATE TEMPORARY TABLE __temp__users AS SELECT FROM users", - 'DROP TABLE "users"', - "CREATE TABLE \"users\" (\"status\" VARCHAR(255) CHECK(status IN ('active', 'inactive')) NOT NULL DEFAULT 'active')", - 'INSERT INTO "users" ("status") SELECT status FROM __temp__users', - "DROP TABLE __temp__users", - ] - - self.assertEqual(blueprint.to_sql(), sql) diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py b/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py index 87bd3511..768bac01 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py +++ b/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/builder.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING from fastapi_startkit.masoniteorm.expressions.expressions import ( + JoinClause, QueryExpression, SelectExpression, UpdateQueryExpression, @@ -27,6 +28,7 @@ def __init__(self, connection: "Connection", grammar, processor): self._table = "" self._limit = False self._wheres = [] + self._joins = () self._sql = "" self._bindings = () @@ -116,6 +118,7 @@ def get_grammar(self): table=self._table, limit=self._limit, wheres=self._wheres, + joins=self._joins, ) def to_qmark(self) -> str: @@ -202,3 +205,44 @@ def or_where(self, column, *args) -> "QueryBuilder": (QueryExpression(column, operator, value, "value", keyword="or")), ) return self + + def join(self, table: str, column1: str, equality: str, column2: str, clause: str = "join") -> "QueryBuilder": + join_clause = JoinClause(table, clause=clause) + join_clause.on(column1, equality, column2) + self._joins += (join_clause,) + return self + + def where_column(self, column1: str, column2: str) -> "QueryBuilder": + self._wheres += (QueryExpression(column1, "=", column2, "value_equals"),) + return self + + def when(self, condition, callback) -> "QueryBuilder": + if condition: + callback(self) + return self + + def where_exists(self, builder: "QueryBuilder") -> "QueryBuilder": + self._wheres += (QueryExpression(None, "EXISTS", SubSelectExpression(builder)),) + return self + + def or_where_exists(self, builder: "QueryBuilder") -> "QueryBuilder": + self._wheres += ( + QueryExpression(None, "EXISTS", SubSelectExpression(builder), keyword="or"), + ) + return self + + def where_has(self, relation: str, callback=None) -> "QueryBuilder": + related = getattr(self._model.__class__, relation) + if callback: + related.query_where_exists(self, callback, method="where_exists") + else: + related.query_has(self, method="where_exists") + return self + + def or_where_has(self, relation: str, callback=None) -> "QueryBuilder": + related = getattr(self._model.__class__, relation) + if callback: + related.query_where_exists(self, callback, method="or_where_exists") + else: + related.query_has(self, method="or_where_exists") + return self diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/model.py b/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/model.py index d86e5d60..8d48e8a2 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/model.py +++ b/fastapi_startkit/src/fastapi_startkit/masoniteorm/models/model.py @@ -50,7 +50,7 @@ def __init_subclass__(cls, **kwargs): def __init__(self, attributes: dict = None, **kwargs): super().__init__(attributes, **kwargs) - self.connection = "default" + self.connection = getattr(self.__class__, "__connection__", "default") self._global_scopes = {} self.__with__ = {} self._exists = False @@ -82,6 +82,14 @@ def get_related(self, key: str): def with_(cls, *eagers) -> "QueryBuilder": return cls.query().with_(*eagers) + @classmethod + def where_has(cls, relation: str, callback=None) -> "QueryBuilder": + return cls.query().where_has(relation, callback) + + @classmethod + def or_where_has(cls, relation: str, callback=None) -> "QueryBuilder": + return cls.query().or_where_has(relation, callback) + @classmethod async def find(cls, primary_key: str | int, columns=None): return await cls.query().find(primary_key, columns) diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/schema/Blueprint.py b/fastapi_startkit/src/fastapi_startkit/masoniteorm/schema/Blueprint.py index 36181512..b2d566eb 100644 --- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/schema/Blueprint.py +++ b/fastapi_startkit/src/fastapi_startkit/masoniteorm/schema/Blueprint.py @@ -754,7 +754,7 @@ def to_sql(self): elif self._action == "create_table_if_not_exists": return self.platform().compile_create_sql(self.table, if_not_exists=True) else: - if not self._dry: + if not self._dry and self.table.from_table is None: # get current table schema table = self.platform().get_current_schema( self.connection, self.table.name, schema=self.schema diff --git a/fastapi_startkit/tests/masoniteorm/fixtures/db.py b/fastapi_startkit/tests/masoniteorm/fixtures/db.py index 77a3453c..25a88f94 100644 --- a/fastapi_startkit/tests/masoniteorm/fixtures/db.py +++ b/fastapi_startkit/tests/masoniteorm/fixtures/db.py @@ -11,6 +11,10 @@ "driver": "sqlite", "url": "sqlite+aiosqlite:///masonite.sqlite3", }, + "dev": { + "driver": "sqlite", + "url": "sqlite+aiosqlite:///masonite_dev.sqlite3", + }, }, }, ) diff --git a/fastapi_startkit/tests/masoniteorm/fixtures/migration.py b/fastapi_startkit/tests/masoniteorm/fixtures/migration.py index 39839b0f..71764712 100644 --- a/fastapi_startkit/tests/masoniteorm/fixtures/migration.py +++ b/fastapi_startkit/tests/masoniteorm/fixtures/migration.py @@ -4,9 +4,10 @@ async def wipe(): - tables = await schema.on("default").get_all_tables() - for table in tables: - await schema.on("default").drop_table_if_exists(table) + for connection in ("default", "dev"): + tables = await schema.on(connection).get_all_tables() + for table in tables: + await schema.on(connection).drop_table_if_exists(table) async def migrate(): @@ -60,3 +61,17 @@ async def migrate(): table.integer("store_id") table.integer("product_id") table.timestamps() + + async with await schema.on("dev").create_table_if_not_exists("countries") as table: + table.integer("country_id").primary() + table.string("name") + + async with await schema.on("dev").create_table_if_not_exists("ports") as table: + table.integer("port_id").primary() + table.string("name") + table.integer("port_country_id") + + async with await schema.on("dev").create_table_if_not_exists("incoming_shipments") as table: + table.integer("shipment_id").primary() + table.string("name") + table.integer("from_port_id") diff --git a/fastapi_startkit/tests/masoniteorm/fixtures/model.py b/fastapi_startkit/tests/masoniteorm/fixtures/model.py index 928e774d..035ee6d7 100644 --- a/fastapi_startkit/tests/masoniteorm/fixtures/model.py +++ b/fastapi_startkit/tests/masoniteorm/fixtures/model.py @@ -5,6 +5,7 @@ BelongsTo, HasMany, BelongsToMany, + HasOneThrough, ) from fastapi_startkit.masoniteorm.models.model import Model @@ -55,3 +56,26 @@ class Store(Model): class Product(Model): __table__ = "products" + + +class Port(Model): + __table__ = "ports" + __connection__ = "dev" + + +class Country(Model): + __table__ = "countries" + __connection__ = "dev" + + +class IncomingShipment(Model): + __table__ = "incoming_shipments" + __connection__ = "dev" + + from_country: "Country" = HasOneThrough( + ["Country", "Port"], + "from_port_id", # FK on IncomingShipment → Port + "port_country_id", # FK on Port → Country + "port_id", # PK on Port + "country_id", # PK on Country + ) diff --git a/fastapi_startkit/tests/masoniteorm/fixtures/seeder.py b/fastapi_startkit/tests/masoniteorm/fixtures/seeder.py index f0a87c42..8d4d5f47 100644 --- a/fastapi_startkit/tests/masoniteorm/fixtures/seeder.py +++ b/fastapi_startkit/tests/masoniteorm/fixtures/seeder.py @@ -1,4 +1,4 @@ -from .model import User, Profile, Articles, Logo +from .model import User, Profile, Articles, Logo, Country, Port, IncomingShipment async def seeder(): @@ -16,3 +16,37 @@ async def seeder(): await Logo.create( {"article_id": article.id, "published_date": "2020-01-01 00:00:00"} ) + + await Country.query().insert( + [ + {"country_id": 10, "name": "Australia"}, + {"country_id": 20, "name": "USA"}, + {"country_id": 30, "name": "Canada"}, + {"country_id": 40, "name": "United Kingdom"}, + ] + ) + + await Port.query().insert( + [ + {"port_id": 100, "name": "Melbourne", "port_country_id": 10}, + {"port_id": 200, "name": "Darwin", "port_country_id": 10}, + {"port_id": 300, "name": "South Louisiana", "port_country_id": 20}, + {"port_id": 400, "name": "Houston", "port_country_id": 20}, + {"port_id": 500, "name": "Montreal", "port_country_id": 30}, + {"port_id": 600, "name": "Vancouver", "port_country_id": 30}, + {"port_id": 700, "name": "Southampton", "port_country_id": 40}, + {"port_id": 800, "name": "London Gateway", "port_country_id": 40}, + ] + ) + + await IncomingShipment.query().insert( + [ + {"name": "Bread", "from_port_id": 300}, + {"name": "Milk", "from_port_id": 100}, + {"name": "Tractor Parts", "from_port_id": 100}, + {"name": "Fridges", "from_port_id": 700}, + {"name": "Wheat", "from_port_id": 600}, + {"name": "Kettles", "from_port_id": 400}, + {"name": "Bread", "from_port_id": 700}, + ] + ) diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/relationships/test_sqlite_has_many_through_relationship.py b/fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_has_many_through_relationship.py similarity index 100% rename from fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/relationships/test_sqlite_has_many_through_relationship.py rename to fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_has_many_through_relationship.py diff --git a/fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_has_one_through_relationship.py b/fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_has_one_through_relationship.py new file mode 100644 index 00000000..cf7bed29 --- /dev/null +++ b/fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_has_one_through_relationship.py @@ -0,0 +1,54 @@ +from ...fixtures.model import Country, IncomingShipment +from ..test_case import TestCase + + +class TestHasOneThroughRelationship(TestCase): + async def test_has_one_through_can_eager_load(self): + shipments = await ( + IncomingShipment.where("name", "Bread").with_("from_country").get() + ) + assert shipments.count() == 2 + + shipment1 = shipments.shift() + assert isinstance(shipment1.from_country, Country) + assert shipment1.from_country.country_id == 20 + + shipment2 = shipments.shift() + assert isinstance(shipment2.from_country, Country) + assert shipment2.from_country.country_id == 40 + + # check .first() and .get() produce the same result + single = await ( + IncomingShipment.where("name", "Tractor Parts") + .with_("from_country") + .first() + ) + single_get = await ( + IncomingShipment.where("name", "Tractor Parts").with_("from_country").get() + ) + assert single.from_country.country_id == 10 + assert single_get.count() == 1 + assert ( + single.from_country.country_id == single_get.first().from_country.country_id + ) + + async def test_has_one_through_eager_load_can_be_empty(self): + shipments = await ( + IncomingShipment.where("name", "Bread") + .where_has("from_country", lambda query: query.where("name", "Uruguay")) + .with_("from_country") + .get() + ) + assert shipments.count() == 0 + + async def test_has_one_through_can_get_related(self): + shipment = await IncomingShipment.where("name", "Milk").first() + country = await shipment.from_country + assert isinstance(country, Country) + assert country.country_id == 10 + + async def test_has_one_through_has_query(self): + shipments = await IncomingShipment.where_has( + "from_country", lambda query: query.where("name", "USA") + ).get() + assert shipments.count() == 2 diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/relationships/test_sqlite_polymorphic.py b/fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_polymorphic.py similarity index 100% rename from fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/relationships/test_sqlite_polymorphic.py rename to fastapi_startkit/tests/masoniteorm/sqlite/relationships/test_sqlite_polymorphic.py diff --git a/fastapi_startkit/tests/masoniteorm/sqlite/schema/test_sqlite_schema_builder_alter.py b/fastapi_startkit/tests/masoniteorm/sqlite/schema/test_sqlite_schema_builder_alter.py new file mode 100644 index 00000000..005804c9 --- /dev/null +++ b/fastapi_startkit/tests/masoniteorm/sqlite/schema/test_sqlite_schema_builder_alter.py @@ -0,0 +1,268 @@ +from unittest.mock import AsyncMock, MagicMock + +from fastapi_startkit.masoniteorm.schema.Table import Table + +from ..test_case import TestCase + + +class TestSQLiteSchemaBuilderAlter(TestCase): + async def test_can_add_columns(self): + mock_statement = AsyncMock() + conn = self.schema.get_connection() + conn.statement = mock_statement + conn.query = MagicMock(return_value=[]) + + async with await self.schema.table("users") as blueprint: + blueprint.string("name") + blueprint.string("external_type").default("external") + blueprint.integer("age") + + self.assertEqual(len(blueprint.table.added_columns), 3) + self.assertEqual( + blueprint.to_sql(), + [ + 'ALTER TABLE "users" ADD COLUMN "name" VARCHAR NOT NULL', + """ALTER TABLE "users" ADD COLUMN "external_type" VARCHAR NOT NULL DEFAULT 'external'""", + 'ALTER TABLE "users" ADD COLUMN "age" INTEGER NOT NULL', + ], + ) + + async def test_can_add_constraints(self): + mock_statement = AsyncMock() + conn = self.schema.get_connection() + conn.statement = mock_statement + conn.query = MagicMock(return_value=[]) + + async with await self.schema.table("users") as blueprint: + blueprint.unique("name", name="table_unique") + + self.assertEqual(len(blueprint.table.added_columns), 0) + self.assertEqual( + blueprint.to_sql(), + ['CREATE UNIQUE INDEX table_unique ON "users"(name)'], + ) + + async def test_alter_rename(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + table = Table("users") + table.add_column("post", "integer") + + async with await self.schema.table("users") as blueprint: + blueprint.rename("post", "comment", "integer") + blueprint.table.from_table = table + + self.assertEqual( + blueprint.to_sql(), + [ + "CREATE TEMPORARY TABLE __temp__users AS SELECT post FROM users", + 'DROP TABLE "users"', + 'CREATE TABLE "users" ("comment" INTEGER NOT NULL)', + 'INSERT INTO "users" ("comment") SELECT post FROM __temp__users', + "DROP TABLE __temp__users", + ], + ) + + async def test_alter_drop(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + table = Table("users") + table.add_column("post", "string") + table.add_column("name", "string") + table.add_column("email", "string") + + async with await self.schema.table("users") as blueprint: + blueprint.drop_column("post") + blueprint.table.from_table = table + + self.assertEqual( + blueprint.to_sql(), + [ + "CREATE TEMPORARY TABLE __temp__users AS SELECT name, email FROM users", + 'DROP TABLE "users"', + 'CREATE TABLE "users" ("name" VARCHAR NOT NULL, "email" VARCHAR NOT NULL)', + 'INSERT INTO "users" ("name", "email") SELECT name, email FROM __temp__users', + "DROP TABLE __temp__users", + ], + ) + + async def test_change(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + table = Table("users") + table.add_column("age", "string") + + async with await self.schema.table("users") as blueprint: + blueprint.integer("age").change() + blueprint.string("name") + blueprint.table.from_table = table + + self.assertEqual(len(blueprint.table.added_columns), 1) + self.assertEqual(len(blueprint.table.changed_columns), 1) + self.assertEqual( + blueprint.to_sql(), + [ + 'ALTER TABLE "users" ADD COLUMN "name" VARCHAR NOT NULL', + "CREATE TEMPORARY TABLE __temp__users AS SELECT age FROM users", + 'DROP TABLE "users"', + 'CREATE TABLE "users" ("age" INTEGER NOT NULL, "name" VARCHAR(255) NOT NULL)', + 'INSERT INTO "users" ("age") SELECT age FROM __temp__users', + "DROP TABLE __temp__users", + ], + ) + + async def test_drop_add_and_change(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + table = Table("users") + table.add_column("age", "string") + table.add_column("email", "string") + + async with await self.schema.table("users") as blueprint: + blueprint.integer("age").change() + blueprint.string("name") + blueprint.drop_column("email") + blueprint.table.from_table = table + + self.assertEqual(len(blueprint.table.added_columns), 1) + self.assertEqual(len(blueprint.table.changed_columns), 1) + + async def test_timestamp_alter_add_nullable_column(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + table = Table("users") + table.add_column("age", "string") + + async with await self.schema.table("users") as blueprint: + blueprint.timestamp("due_date").nullable() + blueprint.table.from_table = table + + self.assertEqual(len(blueprint.table.added_columns), 1) + self.assertEqual( + blueprint.to_sql(), + ['ALTER TABLE "users" ADD COLUMN "due_date" TIMESTAMP NULL'], + ) + + async def test_alter_drop_on_table_schema_table(self): + mock_statement = AsyncMock() + conn = self.schema.get_connection() + conn.statement = mock_statement + conn.query = MagicMock(return_value=[]) + + async with await self.schema.table("table_schema") as blueprint: + blueprint.drop_column("name") + + async with await self.schema.table("table_schema") as blueprint: + blueprint.string("name").nullable() + + async def test_alter_add_primary(self): + mock_statement = AsyncMock() + conn = self.schema.get_connection() + conn.statement = mock_statement + conn.query = MagicMock(return_value=[]) + + async with await self.schema.table("users") as blueprint: + blueprint.primary("playlist_id") + + self.assertEqual( + blueprint.to_sql(), + [ + 'ALTER TABLE "users" ADD CONSTRAINT users_playlist_id_primary PRIMARY KEY (playlist_id)' + ], + ) + + async def test_alter_add_column_and_foreign_key(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + table = Table("users") + table.add_column("age", "string") + table.add_column("email", "string") + + async with await self.schema.table("users") as blueprint: + blueprint.unsigned_integer("playlist_id").nullable() + blueprint.foreign("playlist_id").references("id").on("playlists").on_delete( + "cascade" + ).on_update("SET NULL") + blueprint.table.from_table = table + + self.assertEqual( + blueprint.to_sql(), + [ + 'ALTER TABLE "users" ADD COLUMN "playlist_id" INTEGER UNSIGNED 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, ' + '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", + ], + ) + + async def test_alter_add_foreign_key_only(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + table = Table("users") + table.add_column("age", "string") + table.add_column("email", "string") + + async with await self.schema.table("users") as blueprint: + blueprint.foreign("playlist_id").references("id").on("playlists").on_delete( + "cascade" + ).on_update("set null") + blueprint.table.from_table = table + + self.assertEqual( + blueprint.to_sql(), + [ + "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, ' + '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", + ], + ) + + async def test_can_add_column_enum(self): + mock_statement = AsyncMock() + conn = self.schema.get_connection() + conn.statement = mock_statement + conn.query = MagicMock(return_value=[]) + + async with await self.schema.table("users") as blueprint: + blueprint.enum("status", ["active", "inactive"]).default("active") + + self.assertEqual(len(blueprint.table.added_columns), 1) + self.assertEqual( + blueprint.to_sql(), + [ + "ALTER TABLE \"users\" ADD COLUMN \"status\" VARCHAR CHECK('status' IN('active', 'inactive')) NOT NULL DEFAULT 'active'" + ], + ) + + async def test_can_change_column_enum(self): + mock_statement = AsyncMock() + self.schema.get_connection().statement = mock_statement + + async with await self.schema.table("users") as blueprint: + blueprint.enum("status", ["active", "inactive"]).default("active").change() + blueprint.table.from_table = Table("users") + + self.assertEqual(len(blueprint.table.changed_columns), 1) + self.assertEqual( + blueprint.to_sql(), + [ + "CREATE TEMPORARY TABLE __temp__users AS SELECT FROM users", + 'DROP TABLE "users"', + "CREATE TABLE \"users\" (\"status\" VARCHAR(255) CHECK(status IN ('active', 'inactive')) NOT NULL DEFAULT 'active')", + 'INSERT INTO "users" ("status") SELECT status FROM __temp__users', + "DROP TABLE __temp__users", + ], + ) diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/schema/test_table.py b/fastapi_startkit/tests/masoniteorm/sqlite/schema/test_table.py similarity index 100% rename from fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/schema/test_table.py rename to fastapi_startkit/tests/masoniteorm/sqlite/schema/test_table.py diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/schema/test_table_diff.py b/fastapi_startkit/tests/masoniteorm/sqlite/schema/test_table_diff.py similarity index 100% rename from fastapi_startkit/src/fastapi_startkit/masoniteorm.backup/tests/sqlite/schema/test_table_diff.py rename to fastapi_startkit/tests/masoniteorm/sqlite/schema/test_table_diff.py