From 7b3c3b92c6b69a2ead162b5564c1feb2480c9a52 Mon Sep 17 00:00:00 2001 From: Ronen Lubin <63970571+ronenlu@users.noreply.github.com> Date: Thu, 25 Jan 2024 10:42:58 +0200 Subject: [PATCH] support mssql driver (#15) --- .github/workflows/ci.yaml | 6 +- README.md | 3 +- .../commands/atlas-provider-django.py | 9 +++ poetry.lock | 69 ++++++++++++++++++- pyproject.toml | 2 + tests/atlas.hcl | 1 + tests/expected_all_apps.sql | 4 -- tests/expected_app1.sql | 2 - tests/migrations/mssql/20240125082659.sql | 42 +++++++++++ tests/migrations/mssql/atlas.sum | 2 + 10 files changed, 130 insertions(+), 10 deletions(-) create mode 100644 tests/migrations/mssql/20240125082659.sql create mode 100644 tests/migrations/mssql/atlas.sum diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6ab1bca..25bff98 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -32,7 +32,7 @@ jobs: integration-tests: strategy: matrix: - dialect: [ mysql, postgresql, sqlite ] + dialect: [ mysql, postgresql, sqlite, mssql ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -55,10 +55,12 @@ jobs: run: poetry install - name: Install atlas uses: ariga/setup-atlas@master - - name: Run Test as Standalone + - name: Run migrate diff working-directory: ./tests run: | atlas migrate diff --env django --var dialect=${{ matrix.dialect }} + env: + ATLAS_TOKEN: ${{ secrets.ATLAS_TOKEN }} - name: Verify migrations generated working-directory: ./tests run: | diff --git a/README.md b/README.md index fab2399..e0baf43 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ data "external_schema" "django" { "python", "manage.py", "atlas-provider-django", - "--dialect", "mysql" // mariadb | postgresql | sqlite + "--dialect", "mysql" // mariadb | postgresql | sqlite | mssql // if you want to only load a subset of your app models, you can specify the apps by adding // "--apps", "app1", "app2", "app3" ] @@ -94,6 +94,7 @@ The provider supports the following databases: * MariaDB * PostgreSQL * SQLite +* Microsoft SQL Server ### Issues diff --git a/atlas_provider_django/management/commands/atlas-provider-django.py b/atlas_provider_django/management/commands/atlas-provider-django.py index 37ca551..bff26cc 100644 --- a/atlas_provider_django/management/commands/atlas-provider-django.py +++ b/atlas_provider_django/management/commands/atlas-provider-django.py @@ -24,6 +24,7 @@ class Dialect(str, Enum): mariadb = "mariadb" sqlite = "sqlite" postgresql = "postgresql" + mssql = "mssql" def __str__(self): return self.value @@ -110,6 +111,13 @@ def get_connection_by_dialect(dialect): }, "mysql") conn.SchemaEditorClass = MockMySQLDatabaseSchemaEditor conn.features = MockMariaDBDatabaseFeatures(conn) + case Dialect.mssql: + # import dynamically to avoid unixodbc not installed error on mac os for other dialects + from mssql.base import DatabaseWrapper as MSSQLDatabaseWrapper + conn = MSSQLDatabaseWrapper({ + "ENGINE": "mssql", + "OPTIONS": {}, + }, "mssql") return conn @@ -158,6 +166,7 @@ def collect_sql(self, plan): # Copied from Django's sqlmigrate command: https://github.com/django/django/blob/8a1727dc7f66db7f0131d545812f77544f35aa57/django/core/management/commands/sqlmigrate.py#L40-L83 # Code licensed under the BSD 3-Clause License: https://github.com/django/django/blob/main/LICENSE def mock_handle(self, *args, **options): + self.output_transaction = False connection = get_connection_by_dialect(current_dialect) loader = MockMigrationLoader(connection, replace_migrations=False, load=False) loader.build_graph() diff --git a/poetry.lock b/poetry.lock index c77d231..0a54d41 100644 --- a/poetry.lock +++ b/poetry.lock @@ -34,6 +34,22 @@ tzdata = {version = "*", markers = "sys_platform == \"win32\""} argon2 = ["argon2-cffi (>=19.1.0)"] bcrypt = ["bcrypt"] +[[package]] +name = "mssql-django" +version = "1.4" +description = "Django backend for Microsoft SQL Server" +optional = false +python-versions = "*" +files = [ + {file = "mssql-django-1.4.tar.gz", hash = "sha256:e3fd0f1d5dd9c177f36869d39a272ee4f538e50dccdcfc7a5fa8014526e72fbe"}, + {file = "mssql_django-1.4-py3-none-any.whl", hash = "sha256:9e9b8d66dae9deacad490bc762c200d9a351e0967100b443f8df1106d926686c"}, +] + +[package.dependencies] +django = ">=3.2,<5.1" +pyodbc = ">=3.0" +pytz = "*" + [[package]] name = "mysqlclient" version = "2.2.1" @@ -133,6 +149,57 @@ files = [ {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, ] +[[package]] +name = "pyodbc" +version = "5.0.1" +description = "DB API module for ODBC" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyodbc-5.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9824b175db875a2dd116c7cf16dc3bdf14855404417afd145c5b839da222cb46"}, + {file = "pyodbc-5.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b4d41d0a10523862aac9e2f578bae0ec66003c76e0644d1b53d6ac110b73e5ed"}, + {file = "pyodbc-5.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd4a9ec6d0b31118f3c46020d9784923b99873585919d08e67d95bbf6ab99716"}, + {file = "pyodbc-5.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:654843036b714e4f3f4605fc359690738a3465d333f8297a74841e3990d3eabd"}, + {file = "pyodbc-5.0.1-cp310-cp310-win32.whl", hash = "sha256:c68b3dd0b86d45fdebc566a9596f27b216d25068728bb43a4c961f0fcb9bb970"}, + {file = "pyodbc-5.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:1d7dbef96ebb3eba8bc3f5431694d6e6969d50d0e21f6c4f3b0b2dc9fe8c4f25"}, + {file = "pyodbc-5.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:459a245ae5409dd2fe6eb8531b907da8d61f72d3b18a6276c82cafe2e4896acf"}, + {file = "pyodbc-5.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b3ea6ed0fb8a1b46e643cfaba935656b9abe023d4ab66a8f653bf0c5bc1ce74d"}, + {file = "pyodbc-5.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbfca542a528278a2c43a85d7a6bfde74854d0e7b3cd8c4ab31647bfe5a11069"}, + {file = "pyodbc-5.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8744c48f494bd365529935d3ac6ec15d3e3995e877587e293a3d3110ffcc0374"}, + {file = "pyodbc-5.0.1-cp311-cp311-win32.whl", hash = "sha256:b22474ede5b2841fe67658431f02a3ab9f16601c12a18c0286af435d742c2b71"}, + {file = "pyodbc-5.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:ccc04742bb2fee1f5d2a5b84462f62cd68c346aa274b89b5257e328d41f9d69d"}, + {file = "pyodbc-5.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9aef4f14f126ef607897245ff2742532ce0c299aee3c0e48d37bbdf1ff0b9532"}, + {file = "pyodbc-5.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbf783d6cc923603de35a648db64c50f6699372188dc32b97a373c4b12694bd1"}, + {file = "pyodbc-5.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2f32d98f9522de54958e3478d03c1a85c1efc103f378913bc4d9a7b610a3d02"}, + {file = "pyodbc-5.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88d94c4fa53fc5440ce904c987e97316f271c21d3e968090f8fb76baf1a9be90"}, + {file = "pyodbc-5.0.1-cp312-cp312-win32.whl", hash = "sha256:17d25a51823a5c8631fd117c1d352425404cd2116fd75b55aac4cb79da2e8c85"}, + {file = "pyodbc-5.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:3a3204ba4d1374fe8c301a76e04c9e9207d71cc346ca8282a85c20260d135b5d"}, + {file = "pyodbc-5.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9ae1d8ba8cbf3680556f147f89948493d3f5bd1ed360bd5ce9807e62e573ed03"}, + {file = "pyodbc-5.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:51162fa1902657a555f5b661eb36516c12b6c11ec84db77c9da3aabc7325fa95"}, + {file = "pyodbc-5.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3cf639c73fb948fa2744e52de316b033b95f852960aae58b63c7dc32a88b4d2"}, + {file = "pyodbc-5.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:067015537a20cf893ae675384f99291eade66a7d59bc54f9dedee55ad4290b4c"}, + {file = "pyodbc-5.0.1-cp38-cp38-win32.whl", hash = "sha256:e7ef83973ba56f01e95579ce74c24b72bbd918daf417f33726e21cc01a5bf53c"}, + {file = "pyodbc-5.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:4e5e4e2b4d5dce80f18d58286f1e7a3e2a79eb49d2c514c56ab043ae00566303"}, + {file = "pyodbc-5.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:393e51ca84ba5a3983f859d50ef029d243cb2c7a5b1ef0ddc36479ff49780078"}, + {file = "pyodbc-5.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d109b7955864f11d5e0c813e6b886a2829422cd7b59c61870dc5e0fa7904f132"}, + {file = "pyodbc-5.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b704e481c5973155eebed3730b9769c349b0338ad6fa7b0d7191cc30f3303e0a"}, + {file = "pyodbc-5.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b38d3adc4c9911b46cff3b1a8293e25c3a18c465d13483624a07bc1056c36a"}, + {file = "pyodbc-5.0.1-cp39-cp39-win32.whl", hash = "sha256:ce553cdf33663c6496175a11f32b684bb38cdffb9b185f3967037d369161715c"}, + {file = "pyodbc-5.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:87df79b524928d8c65475099b1cce3cfea66ef2c332ecb027fa6455c4916ad26"}, + {file = "pyodbc-5.0.1.tar.gz", hash = "sha256:03d7d0b04d5a9156099ce8d03e92f3956783746fa9234eb6f5b5cfc12b645011"}, +] + +[[package]] +name = "pytz" +version = "2023.3.post1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, + {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, +] + [[package]] name = "ruff" version = "0.1.14" @@ -189,4 +256,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "dd165cd42dfaeec167310a9595a2b25d881a776666a3b9943f004baf14330e6b" +content-hash = "1541e1ae801908dec19901027c3f6050c1e7869aa22ddebbc1e1fe30d70dfa9c" diff --git a/pyproject.toml b/pyproject.toml index 03ee8d0..c865dce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,8 @@ python = "^3.11" django = ">=4.2" psycopg2-binary = "^2.9.9" mysqlclient = "^2.2.1" +mssql-django = "^1.4" +pyodbc = "^5.0.1" [tool.poetry.group.dev.dependencies] ruff = "^0.1.14" diff --git a/tests/atlas.hcl b/tests/atlas.hcl index c3168d9..f5f5ce2 100644 --- a/tests/atlas.hcl +++ b/tests/atlas.hcl @@ -6,6 +6,7 @@ locals { dev_url = { mysql = "docker://mysql/8/dev" postgresql = "docker://postgres/15" + mssql = "docker://sqlserver/2022-latest" sqlite = "sqlite://?mode=memory&_fk=1" }[var.dialect] } diff --git a/tests/expected_all_apps.sql b/tests/expected_all_apps.sql index 86ad8ca..fcf6acd 100644 --- a/tests/expected_all_apps.sql +++ b/tests/expected_all_apps.sql @@ -1,4 +1,3 @@ -BEGIN; -- -- Create model Musician -- @@ -8,8 +7,6 @@ CREATE TABLE `app1_musician` (`id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, ` -- CREATE TABLE `app1_album` (`id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(100) NOT NULL, `release_date` date NOT NULL, `num_stars` integer NOT NULL, `artist_id` bigint NOT NULL); ALTER TABLE `app1_album` ADD CONSTRAINT `app1_album_artist_id_aed0987a_fk_app1_musician_id` FOREIGN KEY (`artist_id`) REFERENCES `app1_musician` (`id`); -COMMIT; -BEGIN; -- -- Create model User -- @@ -19,4 +16,3 @@ CREATE TABLE `app2_user` (`id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, `firs -- CREATE TABLE `app2_blog` (`id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(100) NOT NULL, `created_at` date NOT NULL, `num_stars` integer NOT NULL, `author_id` bigint NOT NULL); ALTER TABLE `app2_blog` ADD CONSTRAINT `app2_blog_author_id_1675e606_fk_app2_user_id` FOREIGN KEY (`author_id`) REFERENCES `app2_user` (`id`); -COMMIT; diff --git a/tests/expected_app1.sql b/tests/expected_app1.sql index 20a7b1d..1000607 100644 --- a/tests/expected_app1.sql +++ b/tests/expected_app1.sql @@ -1,4 +1,3 @@ -BEGIN; -- -- Create model Musician -- @@ -8,4 +7,3 @@ CREATE TABLE `app1_musician` (`id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, ` -- CREATE TABLE `app1_album` (`id` bigint AUTO_INCREMENT NOT NULL PRIMARY KEY, `name` varchar(100) NOT NULL, `release_date` date NOT NULL, `num_stars` integer NOT NULL, `artist_id` bigint NOT NULL); ALTER TABLE `app1_album` ADD CONSTRAINT `app1_album_artist_id_aed0987a_fk_app1_musician_id` FOREIGN KEY (`artist_id`) REFERENCES `app1_musician` (`id`); -COMMIT; diff --git a/tests/migrations/mssql/20240125082659.sql b/tests/migrations/mssql/20240125082659.sql new file mode 100644 index 0000000..b982217 --- /dev/null +++ b/tests/migrations/mssql/20240125082659.sql @@ -0,0 +1,42 @@ +-- Create "app1_musician" table +CREATE TABLE [app1_musician] ( + [id] bigint IDENTITY (1, 1) NOT NULL, + [first_name] nvarchar(50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, + [last_name] nvarchar(50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, + [instrument] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, + CONSTRAINT [PK_app1_musician] PRIMARY KEY CLUSTERED ([id] ASC) +); +-- Create "app1_album" table +CREATE TABLE [app1_album] ( + [id] bigint IDENTITY (1, 1) NOT NULL, + [name] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, + [release_date] date NOT NULL, + [num_stars] int NOT NULL, + [artist_id] bigint NOT NULL, + CONSTRAINT [PK_app1_album] PRIMARY KEY CLUSTERED ([id] ASC), + + CONSTRAINT [app1_album_artist_id_aed0987a_fk_app1_musician_id] FOREIGN KEY ([artist_id]) REFERENCES [app1_musician] ([id]) ON UPDATE NO ACTION ON DELETE NO ACTION +); +-- Create index "app1_album_artist_id_aed0987a" to table: "app1_album" +CREATE NONCLUSTERED INDEX [app1_album_artist_id_aed0987a] ON [app1_album] ([artist_id] ASC); +-- Create "app2_user" table +CREATE TABLE [app2_user] ( + [id] bigint IDENTITY (1, 1) NOT NULL, + [first_name] nvarchar(50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, + [last_name] nvarchar(50) COLLATE SQL_Latin1_General_CP1_CI_AS NULL, + [roll] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, + CONSTRAINT [PK_app2_user] PRIMARY KEY CLUSTERED ([id] ASC) +); +-- Create "app2_blog" table +CREATE TABLE [app2_blog] ( + [id] bigint IDENTITY (1, 1) NOT NULL, + [name] nvarchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, + [created_at] date NOT NULL, + [num_stars] int NOT NULL, + [author_id] bigint NOT NULL, + CONSTRAINT [PK_app2_blog] PRIMARY KEY CLUSTERED ([id] ASC), + + CONSTRAINT [app2_blog_author_id_1675e606_fk_app2_user_id] FOREIGN KEY ([author_id]) REFERENCES [app2_user] ([id]) ON UPDATE NO ACTION ON DELETE NO ACTION +); +-- Create index "app2_blog_author_id_1675e606" to table: "app2_blog" +CREATE NONCLUSTERED INDEX [app2_blog_author_id_1675e606] ON [app2_blog] ([author_id] ASC); diff --git a/tests/migrations/mssql/atlas.sum b/tests/migrations/mssql/atlas.sum new file mode 100644 index 0000000..a4dba52 --- /dev/null +++ b/tests/migrations/mssql/atlas.sum @@ -0,0 +1,2 @@ +h1:sRFOptG6Ur6mPNfVzxYWfimbzsOprjUnLDRHQgmlr+4= +20240125082659.sql h1:pSoj2u057jC6SZu95JIzvtbGrJzFuY7AW8Ym2ErCnag=