44from django .db import DatabaseError
55import pyodbc as Database
66
7+ from collections import namedtuple
8+
79from django import VERSION
8- from django .db .backends .base .introspection import (
9- BaseDatabaseIntrospection , FieldInfo , TableInfo ,
10- )
10+ from django .db .backends .base .introspection import BaseDatabaseIntrospection
11+ from django . db . backends . base . introspection import FieldInfo as BaseFieldInfo
12+ from django . db . backends . base . introspection import TableInfo as BaseTableInfo
1113from django .db .models .indexes import Index
1214from django .conf import settings
1315
1416SQL_AUTOFIELD = - 777555
1517SQL_BIGAUTOFIELD = - 777444
18+ SQL_SMALLAUTOFIELD = - 777333
1619SQL_TIMESTAMP_WITH_TIMEZONE = - 155
1720
21+ FieldInfo = namedtuple ("FieldInfo" , BaseFieldInfo ._fields + ("comment" ,))
22+ TableInfo = namedtuple ("TableInfo" , BaseTableInfo ._fields + ("comment" ,))
1823
1924def get_schema_name ():
2025 return getattr (settings , 'SCHEMA_TO_INSPECT' , 'SCHEMA_NAME()' )
@@ -25,6 +30,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
2530 data_types_reverse = {
2631 SQL_AUTOFIELD : 'AutoField' ,
2732 SQL_BIGAUTOFIELD : 'BigAutoField' ,
33+ SQL_SMALLAUTOFIELD : 'SmallAutoField' ,
2834 Database .SQL_BIGINT : 'BigIntegerField' ,
2935 # Database.SQL_BINARY: ,
3036 Database .SQL_BIT : 'BooleanField' ,
@@ -71,13 +77,26 @@ def get_table_list(self, cursor):
7177 """
7278 Returns a list of table and view names in the current database.
7379 """
74- sql = 'SELECT TABLE_NAME, TABLE_TYPE FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = %s' % (
80+ sql = """SELECT
81+ TABLE_NAME,
82+ TABLE_TYPE,
83+ CAST(ep.value AS VARCHAR) AS COMMENT
84+ FROM INFORMATION_SCHEMA.TABLES i
85+ LEFT JOIN sys.tables t ON t.name = i.TABLE_NAME
86+ LEFT JOIN sys.extended_properties ep ON t.object_id = ep.major_id
87+ AND ((ep.name = 'MS_DESCRIPTION' AND ep.minor_id = 0) OR ep.value IS NULL)
88+ AND i.TABLE_SCHEMA = %s""" % (
7589 get_schema_name ())
7690 cursor .execute (sql )
7791 types = {'BASE TABLE' : 't' , 'VIEW' : 'v' }
78- return [TableInfo (row [0 ], types .get (row [1 ]))
79- for row in cursor .fetchall ()
80- if row [0 ] not in self .ignored_tables ]
92+ if VERSION >= (4 , 2 ):
93+ return [TableInfo (row [0 ], types .get (row [1 ]), row [2 ])
94+ for row in cursor .fetchall ()
95+ if row [0 ] not in self .ignored_tables ]
96+ else :
97+ return [BaseTableInfo (row [0 ], types .get (row [1 ]))
98+ for row in cursor .fetchall ()
99+ if row [0 ] not in self .ignored_tables ]
81100
82101 def _is_auto_field (self , cursor , table_name , column_name ):
83102 """
@@ -111,7 +130,7 @@ def get_table_description(self, cursor, table_name, identity_check=True):
111130
112131 if not columns :
113132 raise DatabaseError (f"Table { table_name } does not exist." )
114-
133+
115134 items = []
116135 for column in columns :
117136 if VERSION >= (3 , 2 ):
@@ -126,15 +145,40 @@ def get_table_description(self, cursor, table_name, identity_check=True):
126145 column .append (collation_name [0 ] if collation_name else '' )
127146 else :
128147 column .append ('' )
129-
148+ if VERSION >= (4 , 2 ):
149+ sql = """select CAST(ep.value AS VARCHAR) AS COMMENT
150+ FROM sys.columns c
151+ INNER JOIN sys.tables t ON c.object_id = t.object_id
152+ INNER JOIN sys.extended_properties ep ON c.object_id=ep.major_id AND ep.minor_id = c.column_id
153+ WHERE t.name = '%s' AND c.name = '%s' AND ep.name = 'MS_Description'
154+ """ % (table_name , column [0 ])
155+ cursor .execute (sql )
156+ comment = cursor .fetchone ()
157+ column .append (comment [0 ] if comment else '' )
130158 if identity_check and self ._is_auto_field (cursor , table_name , column [0 ]):
131159 if column [1 ] == Database .SQL_BIGINT :
132160 column [1 ] = SQL_BIGAUTOFIELD
161+ elif column [1 ] == Database .SQL_SMALLINT :
162+ column [1 ] = SQL_SMALLAUTOFIELD
133163 else :
134164 column [1 ] = SQL_AUTOFIELD
135165 if column [1 ] == Database .SQL_WVARCHAR and column [3 ] < 4000 :
136166 column [1 ] = Database .SQL_WCHAR
137- items .append (FieldInfo (* column ))
167+ # Remove surrounding parentheses for default values
168+ if column [7 ]:
169+ default_value = column [7 ]
170+ start = 0
171+ end = - 1
172+ for _ in range (2 ):
173+ if default_value [start ] == '(' and default_value [end ] == ')' :
174+ start += 1
175+ end -= 1
176+ column [7 ] = default_value [start :end + 1 ]
177+
178+ if VERSION >= (4 , 2 ):
179+ items .append (FieldInfo (* column ))
180+ else :
181+ items .append (BaseFieldInfo (* column ))
138182 return items
139183
140184 def get_sequences (self , cursor , table_name , table_fields = ()):
@@ -271,6 +315,7 @@ def get_constraints(self, cursor, table_name):
271315 # Potentially misleading: primary key and unique constraints still have indexes attached to them.
272316 # Should probably be updated with the additional info from the sys.indexes table we fetch later on.
273317 "index" : False ,
318+ "default" : False ,
274319 }
275320 # Record the details
276321 constraints [constraint ]['columns' ].append (column )
@@ -298,6 +343,32 @@ def get_constraints(self, cursor, table_name):
298343 "foreign_key" : None ,
299344 "check" : True ,
300345 "index" : False ,
346+ "default" : False ,
347+ }
348+ # Record the details
349+ constraints [constraint ]['columns' ].append (column )
350+ # Now get DEFAULT constraint columns
351+ cursor .execute ("""
352+ SELECT
353+ [name],
354+ COL_NAME([parent_object_id], [parent_column_id])
355+ FROM
356+ [sys].[default_constraints]
357+ WHERE
358+ OBJECT_NAME([parent_object_id]) = %s
359+ """ , [table_name ])
360+ for constraint , column in cursor .fetchall ():
361+ # If we're the first column, make the record
362+ if constraint not in constraints :
363+ constraints [constraint ] = {
364+ "columns" : [],
365+ "primary_key" : False ,
366+ "unique" : False ,
367+ "unique_constraint" : False ,
368+ "foreign_key" : None ,
369+ "check" : False ,
370+ "index" : False ,
371+ "default" : True ,
301372 }
302373 # Record the details
303374 constraints [constraint ]['columns' ].append (column )
@@ -341,6 +412,7 @@ def get_constraints(self, cursor, table_name):
341412 "unique_constraint" : unique_constraint ,
342413 "foreign_key" : None ,
343414 "check" : False ,
415+ "default" : False ,
344416 "index" : True ,
345417 "orders" : [],
346418 "type" : Index .suffix if type_ in (1 , 2 ) else desc .lower (),
0 commit comments