-
-
Notifications
You must be signed in to change notification settings - Fork 246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MSSQL needs "NOT NULL" sent explicitly for a change in type to maintain not null #977
Comments
is it running a batch "CREATE TABLE" statement ? or you have batch mode left as "auto" so these are ALTER TABLE statements? |
so far this looks like the identical thing that #812 has fixed. We dont render the word "NULL" in the DDL. I'm testing regular op.alter_column(). Not clear if you are forcing batch CREATE TABLE or not, if so I'd advise not using that it isn't well tested outside of SQLite. can't reproduce given behavior at the moment @combinations((True,), (False,), argnames="change_nullability")
def test_alter_column_type_and_nullability_two(self, change_nullability):
context = op_fixture("mssql")
args = dict(
existing_type=String(100),
type_=String(511),
existing_nullable=False,
)
if change_nullability:
args["nullable"] = False
op.alter_column("t", "c", **args)
if change_nullability:
context.assert_(
"ALTER TABLE t ALTER COLUMN c VARCHAR(511) NOT NULL"
)
else:
context.assert_("ALTER TABLE t ALTER COLUMN c VARCHAR(511)") no "NULL" keyword is generated. I also see collation things in your output so please share complete details, thanks. |
I do not set Also, a quick note that the DDL I shows on top was the DDL that dbeaver was showing me when I connect to my mssql and check the DB (not the DDL generated by sqlalchemy) NOTE: I need to support multiple databases: sqlite, mysql, mssql, oracle, postgres |
I've created a more reproducible example. Steps followed: Get a basic alembic setup
Create first migration - add user_notification table
# Add upgrade/downgrade for the first migration manually
$ cat alembic/versions/3905e21d3e98_initial.py
"""initial
Revision ID: 3905e21d3e98
Revises:
Create Date: 2022-01-12 21:21:44.975323
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '3905e21d3e98'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
op.create_table(
'user_notification',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('title', sa.String(length=100), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_user_notification')),
)
def downgrade():
op.drop_table('user_notification')
-- DDL from DBeaver
CREATE TABLE db_empty.dbo.user_notification (
id int IDENTITY(1,1) NOT NULL,
title varchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
CONSTRAINT pk_user_notification PRIMARY KEY (id)
);
Create second migration - change type for user_notification.title
# Add upgrade/downgrade for the second migration manually
$ cat alembic/versions/0e6416b813f6_change_type.py
"""change_type
Revision ID: 0e6416b813f6
Revises: 3905e21d3e98
Create Date: 2022-01-12 21:26:41.093681
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "0e6416b813f6"
down_revision = "3905e21d3e98"
branch_labels = None
depends_on = None
def upgrade():
with op.batch_alter_table("user_notification") as batch_op:
batch_op.alter_column(
"title",
existing_type=sa.String(length=100),
type_=sa.String(length=511),
existing_nullable=False,
)
def downgrade():
with op.batch_alter_table("user_notification") as batch_op:
batch_op.alter_column(
"title",
existing_type=sa.String(length=511),
type_=sa.String(length=100),
existing_nullable=False,
)
-- DDL from Dbeaver:
CREATE TABLE db_empty.dbo.user_notification (
id int IDENTITY(1,1) NOT NULL,
title varchar(511) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
CONSTRAINT pk_user_notification PRIMARY KEY (id)
);
SQL run by alembic-- When I try to generate the SQL from alembic
$ venv/bin/alembic upgrade head --sql
INFO [alembic.runtime.migration] Context impl MSSQLImpl.
INFO [alembic.runtime.migration] Generating static SQL
INFO [alembic.runtime.migration] Will assume transactional DDL.
BEGIN TRANSACTION;
CREATE TABLE alembic_version (
version_num VARCHAR(32) NOT NULL,
CONSTRAINT alembic_version_pkc PRIMARY KEY (version_num)
);
GO
INFO [alembic.runtime.migration] Running upgrade -> 3905e21d3e98, initial
-- Running upgrade -> 3905e21d3e98
CREATE TABLE user_notification (
id INTEGER NOT NULL IDENTITY,
title VARCHAR(100) NOT NULL,
CONSTRAINT pk_user_notification PRIMARY KEY (id)
);
GO
INSERT INTO alembic_version (version_num) OUTPUT inserted.version_num VALUES ('3905e21d3e98');
GO
INFO [alembic.runtime.migration] Running upgrade 3905e21d3e98 -> 0e6416b813f6, change_type
-- Running upgrade 3905e21d3e98 -> 0e6416b813f6
ALTER TABLE user_notification ALTER COLUMN title VARCHAR(511);
GO
UPDATE alembic_version SET version_num='0e6416b813f6' WHERE alembic_version.version_num = '3905e21d3e98';
GO
COMMIT;
GO The differences I see are:
|
As a workaround - I did: with op.batch_alter_table("user_notification") as batch_op:
batch_op.alter_column(
"title",
existing_type=sa.String(length=100),
type_=sa.String(length=511),
nullable=False, # <---------------- Added this line
existing_nullable=False,
) And now the SQL being generated is: ALTER TABLE user_notification ALTER COLUMN title VARCHAR(511) NOT NULL; And both But this workaround does not work with oracle which throws the error:
Looks like MSSQL needs the |
OK so this was the other thing I suspected, MSSQL's ALTER needs us to maintain the nullability with this particular statement, similar to MySQL, so we'd (alembic internals) need to include the directive explicitly for this case. we dont want you to have to set nullable=False since as you can see other DBs will complain. |
cc @gordthompson in case you have experience w/ this aspect of SQL server. ( i probably do also but it's been years). |
Gord Thompson has proposed a fix for this issue in the main branch: Prevent alter_column() from changing nullability https://gerrit.sqlalchemy.org/c/sqlalchemy/alembic/+/3502 |
Describe the bug
Looks like in alembic when we do an
alter_column()
to change the type of a columntype_=
it is ignoring theexisting_nullable=True
and removing the nullable.Expected behavior
I expected the type of my column to change the type - without any other change to the column
To Reproduce
I have a migration like the following in my alembic versions:
db upgrade 5a41b8b16e55
- The DDL shows:title varchar(100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
db upgrade eb57aaff3a51
- The DDL shows:title varchar(511) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
The "NOT NULL" has changed to "NULL" - even though I provided
existing_nullable=False
NOTE: I have other cases in my migrations where I only change the name of the column and not the type - I don't notice this behavior at that time. It seems to be only when I make a change to the type.
Versions.
- OS: CentOS 7
- Python: 3.6.5
- Alembic: 1.7.5
- SQLAlchemy: 1.4.29
- Database: MSSQL 2019
- DBAPI: pyodbc
Additional context
Feels like it may be related to this: #812
But I am using the latest version of alembic as of now
The text was updated successfully, but these errors were encountered: