Skip to content

Commit 3f7ed9e

Browse files
authored
SNOW-118945: Fix ArgumentError where insert with autoincrement failed due to incompatible column type affinity (#297)
* bug fix * fix old test * add autoincrement test * fix tests * review feedbacks * consistent naming
1 parent e2cb627 commit 3f7ed9e

File tree

6 files changed

+104
-10
lines changed

6 files changed

+104
-10
lines changed

DESCRIPTION.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ https://github.com/snowflakedb/snowflake-sqlalchemy
1010
Release Notes
1111
-------------------------------------------------------------------------------
1212

13+
- v1.3.5(Unreleased)
14+
15+
- Fixed a bug where insert with autoincrement failed due to incompatible column type affinity #124
16+
- Fixed a bug when creating a column with sequence, default value was set incorrectly
17+
1318
- v1.3.4(April 27,2022)
1419

1520
- Fixed a bug where identifier max length was set to the wrong value and added relevant schema introspection

base.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from sqlalchemy import util as sa_util
1111
from sqlalchemy.engine import default
12-
from sqlalchemy.schema import Table
12+
from sqlalchemy.schema import Sequence, Table
1313
from sqlalchemy.sql import compiler, expression
1414
from sqlalchemy.sql.elements import quoted_name
1515
from sqlalchemy.util.compat import string_types
@@ -362,7 +362,10 @@ def get_column_specification(self, column, **kwargs):
362362
if column.table is not None \
363363
and column is column.table._autoincrement_column and \
364364
column.server_default is None:
365-
colspec.append('AUTOINCREMENT')
365+
if isinstance(column.default, Sequence):
366+
colspec.append(f"DEFAULT {column.default.name}.nextval")
367+
else:
368+
colspec.append('AUTOINCREMENT')
366369

367370
return ' '.join(colspec)
368371

custom_types.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#
66

77
import sqlalchemy.types as sqltypes
8+
import sqlalchemy.util as util
89

910
TEXT = sqltypes.VARCHAR
1011
CHARACTER = sqltypes.CHAR
@@ -51,3 +52,9 @@ class TIMESTAMP_NTZ(SnowflakeType):
5152

5253
class GEOGRAPHY(SnowflakeType):
5354
__visit_name__ = 'GEOGRAPHY'
55+
56+
57+
class _CUSTOM_DECIMAL(SnowflakeType, sqltypes.DECIMAL):
58+
@util.memoized_property
59+
def _type_affinity(self):
60+
return sqltypes.INTEGER if self.scale == 0 else sqltypes.DECIMAL

snowdialect.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
SnowflakeIdentifierPreparer,
4646
SnowflakeTypeCompiler,
4747
)
48-
from .custom_types import ARRAY, GEOGRAPHY, OBJECT, TIMESTAMP_LTZ, TIMESTAMP_NTZ, TIMESTAMP_TZ, VARIANT
48+
from .custom_types import _CUSTOM_DECIMAL, ARRAY, GEOGRAPHY, OBJECT, TIMESTAMP_LTZ, TIMESTAMP_NTZ, TIMESTAMP_TZ, VARIANT
4949

5050
colspecs = {}
5151

@@ -65,7 +65,7 @@
6565
'FLOAT': FLOAT,
6666
'INT': INTEGER,
6767
'INTEGER': INTEGER,
68-
'NUMBER': DECIMAL,
68+
'NUMBER': _CUSTOM_DECIMAL,
6969
# 'OBJECT': ?
7070
'REAL': REAL,
7171
'BYTEINT': SMALLINT,
@@ -609,7 +609,7 @@ def get_view_definition(self, connection, view_name, schema=None, **kw):
609609
ret = cursor.fetchone()
610610
if ret:
611611
return ret[n2i['text']]
612-
except Exxception:
612+
except Exception:
613613
pass
614614
return None
615615

test/test_quote.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def test_table_name_with_reserved_words(engine_testaccount, db_parameters):
1111
metadata = MetaData()
1212
test_table_name = 'insert'
1313
insert_table = Table(test_table_name, metadata,
14-
Column('id', Integer, Sequence(test_table_name + '_id_seq'),
14+
Column('id', Integer, Sequence(f"{test_table_name}_id_seq"),
1515
primary_key=True),
1616
Column('name', String),
1717
Column('fullname', String),
@@ -22,10 +22,10 @@ def test_table_name_with_reserved_words(engine_testaccount, db_parameters):
2222
inspector = inspect(engine_testaccount)
2323
columns_in_insert = inspector.get_columns(test_table_name)
2424
assert len(columns_in_insert) == 3
25-
assert columns_in_insert[0]['autoincrement'], 'autoincrement'
26-
assert columns_in_insert[0]['default'] is None, 'default'
27-
assert columns_in_insert[0]['name'] == 'id', 'name'
28-
assert columns_in_insert[0]['primary_key'], 'primary key'
25+
assert columns_in_insert[0]['autoincrement'] is False
26+
assert f"{test_table_name}_id_seq.nextval" in columns_in_insert[0]['default'].lower()
27+
assert columns_in_insert[0]['name'] == 'id'
28+
assert columns_in_insert[0]['primary_key']
2929
assert not columns_in_insert[0]['nullable']
3030

3131
columns_in_insert = inspector.get_columns(test_table_name, schema=db_parameters['schema'])

test/test_sequence.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
#
4+
# Copyright (c) 2012-2019 Snowflake Computing Inc. All right reserved.
5+
#
6+
7+
from sqlalchemy import Column, Integer, MetaData, Sequence, String, Table, select
8+
9+
10+
def test_table_with_sequence(engine_testaccount, db_parameters):
11+
# https://github.com/snowflakedb/snowflake-sqlalchemy/issues/124
12+
test_table_name = 'sequence'
13+
test_sequence_name = f'{test_table_name}_id_seq'
14+
sequence_table = Table(test_table_name, MetaData(),
15+
Column('id', Integer, Sequence(test_sequence_name), primary_key=True),
16+
Column('data', String(39))
17+
)
18+
sequence_table.create(engine_testaccount)
19+
seq = Sequence(test_sequence_name)
20+
try:
21+
engine_testaccount.execute(sequence_table.insert(), [{'data': 'test_insert_1'}])
22+
23+
select_stmt = select([sequence_table]).order_by('id')
24+
result = engine_testaccount.execute(select_stmt).fetchall()
25+
assert result == [(1, 'test_insert_1')]
26+
27+
autoload_sequence_table = Table(test_table_name, MetaData(), autoload=True, autoload_with=engine_testaccount)
28+
29+
engine_testaccount.execute(autoload_sequence_table.insert(),
30+
[{'data': 'multi_insert_1'}, {'data': 'multi_insert_2'}])
31+
32+
engine_testaccount.execute(autoload_sequence_table.insert(), [{'data': 'test_insert_2'}])
33+
34+
nextid = engine_testaccount.execute(seq)
35+
engine_testaccount.execute(autoload_sequence_table.insert(), [{'id': nextid, 'data': 'test_insert_seq'}])
36+
result = engine_testaccount.execute(select_stmt).fetchall()
37+
assert result == [
38+
(1, 'test_insert_1'),
39+
(2, 'multi_insert_1'),
40+
(3, 'multi_insert_2'),
41+
(4, 'test_insert_2'),
42+
(5, 'test_insert_seq')
43+
]
44+
finally:
45+
sequence_table.drop(engine_testaccount)
46+
seq.drop(engine_testaccount)
47+
48+
49+
def test_table_with_autoincrement(engine_testaccount, db_parameters):
50+
# https://github.com/snowflakedb/snowflake-sqlalchemy/issues/124
51+
test_table_name = 'sequence'
52+
autoincrement_table = Table(test_table_name, MetaData(),
53+
Column('id', Integer, autoincrement=True, primary_key=True),
54+
Column('data', String(39))
55+
)
56+
autoincrement_table.create(engine_testaccount)
57+
try:
58+
engine_testaccount.execute(autoincrement_table.insert(), [{'data': 'test_insert_1'}])
59+
60+
select_stmt = select([autoincrement_table]).order_by('id')
61+
result = engine_testaccount.execute(select_stmt).fetchall()
62+
assert result == [(1, 'test_insert_1')]
63+
64+
autoload_sequence_table = Table(test_table_name, MetaData(), autoload=True, autoload_with=engine_testaccount)
65+
66+
engine_testaccount.execute(autoload_sequence_table.insert(),
67+
[{'data': 'multi_insert_1'}, {'data': 'multi_insert_2'}])
68+
69+
engine_testaccount.execute(autoload_sequence_table.insert(), [{'data': 'test_insert_2'}])
70+
71+
result = engine_testaccount.execute(select_stmt).fetchall()
72+
assert result == [
73+
(1, 'test_insert_1'),
74+
(2, 'multi_insert_1'),
75+
(3, 'multi_insert_2'),
76+
(4, 'test_insert_2'),
77+
]
78+
finally:
79+
autoincrement_table.drop(engine_testaccount)

0 commit comments

Comments
 (0)