Skip to content

Commit e9f5be0

Browse files
committed
change columns type from string to stream
1 parent 4f42588 commit e9f5be0

File tree

2 files changed

+119
-1
lines changed

2 files changed

+119
-1
lines changed

sqlalchemy_iris/alembic.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,37 @@
11
import logging
22

33
from typing import Optional
4+
from typing import Any
45

56
from sqlalchemy.ext.compiler import compiles
67
from sqlalchemy.sql.base import Executable
78
from sqlalchemy.sql.elements import ClauseElement
9+
from sqlalchemy.sql.type_api import TypeEngine
10+
from sqlalchemy.sql import table
11+
from sqlalchemy import types
812

913
from alembic.ddl import DefaultImpl
1014
from alembic.ddl.base import ColumnNullable
1115
from alembic.ddl.base import ColumnType
1216
from alembic.ddl.base import ColumnName
17+
from alembic.ddl.base import AddColumn
18+
from alembic.ddl.base import DropColumn
1319
from alembic.ddl.base import Column
1420
from alembic.ddl.base import alter_table
1521
from alembic.ddl.base import alter_column
1622
from alembic.ddl.base import format_type
1723
from alembic.ddl.base import format_column_name
18-
1924
from .base import IRISDDLCompiler
2025

2126
log = logging.getLogger(__name__)
2227

28+
# IRIS Interprets these types as %Streams, and no direct type change is available
29+
_as_stream = [
30+
types.LargeBinary,
31+
types.BLOB,
32+
types.CLOB,
33+
]
34+
2335

2436
class IRISImpl(DefaultImpl):
2537
__dialect__ = "iris"
@@ -77,6 +89,60 @@ def drop_column(
7789
self._exec(_ExecDropForeignKey(table_name, fkey[0], schema))
7890
super().drop_column(table_name, column, schema, **kw)
7991

92+
def alter_column(
93+
self,
94+
table_name: str,
95+
column_name: str,
96+
type_: Optional[TypeEngine] = None,
97+
existing_type: Optional[TypeEngine] = None,
98+
schema: Optional[str] = None,
99+
name: Optional[str] = None,
100+
**kw: Any,
101+
) -> None:
102+
if existing_type.__class__ not in _as_stream and type_.__class__ in _as_stream:
103+
"""
104+
To change column type to %Stream
105+
* rename the column with a new name with suffix `__superset_tmp`
106+
* create a new column with the old name
107+
* copy data from an old column to new column
108+
* drop old column
109+
* fix missing parameters, such as nullable
110+
"""
111+
tmp_column = f"{column_name}__superset_tmp"
112+
self._exec(ColumnName(table_name, column_name, tmp_column, schema=schema))
113+
new_kw = {}
114+
self._exec(
115+
AddColumn(
116+
table_name,
117+
Column(column_name, type_=type_, **new_kw),
118+
schema=schema,
119+
)
120+
)
121+
tab = table(
122+
table_name,
123+
Column(column_name, key="new_col"),
124+
Column(tmp_column, key="old_col"),
125+
schema=schema,
126+
)
127+
self._exec(tab.update().values({tab.c.new_col: tab.c.old_col}))
128+
self._exec(DropColumn(table_name, Column(tmp_column), schema=schema))
129+
new_kw = {}
130+
for k in ["server_default", "nullable", "autoincrement"]:
131+
if f"existing_{k}" in kw:
132+
new_kw[k] = kw[f"existing_{k}"]
133+
return super().alter_column(
134+
table_name, column_name, schema=schema, name=name, **new_kw
135+
)
136+
return super().alter_column(
137+
table_name,
138+
column_name,
139+
type_=type_,
140+
existing_type=existing_type,
141+
schema=schema,
142+
name=name,
143+
**kw,
144+
)
145+
80146

81147
class _ExecDropForeignKey(Executable, ClauseElement):
82148
inherit_cache = False

tests/test_alembic.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@
1111
from sqlalchemy import Integer
1212
from sqlalchemy import text
1313
from sqlalchemy.types import Text
14+
from sqlalchemy.types import String
1415
from sqlalchemy.types import LargeBinary
16+
from sqlalchemy_iris.types import LONGVARBINARY
1517

1618
from alembic import op
1719
from alembic.testing import fixture
1820
from alembic.testing import combinations
1921
from alembic.testing import eq_
2022
from alembic.testing.fixtures import TestBase
23+
from alembic.testing.fixtures import TablesTest
2124
from alembic.testing.fixtures import op_fixture
2225
from alembic.testing.suite._autogen_fixtures import AutogenFixtureTest
2326

@@ -70,3 +73,52 @@ def test_drop_col_with_fk(self, ops_context, connection, tables):
7073
ops_context.drop_column("round_trip_table", "oid_fk", self.meta.schema)
7174
insp = inspect(connection)
7275
eq_(insp.get_foreign_keys("round_trip_table", schema=self.meta.schema), [])
76+
77+
# @combinations(
78+
# (None,),
79+
# ("test",),
80+
# argnames="schema",
81+
# id_="s",
82+
# )
83+
class IRISTest(TablesTest):
84+
__only_on__ = "iris"
85+
__backend__ = True
86+
87+
@classmethod
88+
def define_tables(cls, metadata):
89+
Table("tab", metadata, Column("col", String(50), nullable=False))
90+
91+
@classmethod
92+
def insert_data(cls, connection):
93+
connection.execute(
94+
text(
95+
"""
96+
insert into tab (col) values
97+
('some data 1'),
98+
('some data 2'),
99+
('some data 3')
100+
"""
101+
)
102+
)
103+
104+
def test_str_to_blob(self, connection, ops_context):
105+
ops_context.alter_column(
106+
"tab",
107+
"col",
108+
type_=LargeBinary(),
109+
existint_type=String(50),
110+
existing_nullable=False,
111+
)
112+
113+
result = connection.execute(text("select col from tab")).all()
114+
assert result == [
115+
(bytearray(b"'some data 1'"),),
116+
(bytearray(b"'some data 2'"),),
117+
(bytearray(b"'some data 3'"),),
118+
]
119+
120+
insp = inspect(connection)
121+
col = insp.get_columns("tab")[0]
122+
assert col["name"] == "col"
123+
assert isinstance(col["type"], LONGVARBINARY)
124+
assert not col["nullable"]

0 commit comments

Comments
 (0)