diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 568fd40..cdd36c1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ After changes made to the project, it's a good idea to run the unit tests before Download and install SQL Server [here](https://www.microsoft.com/en-us/sql-server/sql-server-downloads), or you could use docker. Change `testapp/settings.py` to match your SQL Server login username and password. ``` - docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=MyPassword42' -p 1433:1433 -d mcr.microsoft.com/mssql/server:2019-latest + docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=Placeholder' -p 1433:1433 -d mcr.microsoft.com/mssql/server:2019-latest ``` 2. **Clone Django** In `mssql-django` folder. @@ -45,4 +45,4 @@ provided by the bot. You will only need to do this once across all repos using o This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or -contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. \ No newline at end of file +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. diff --git a/mssql/base.py b/mssql/base.py index 6896bd4..4d83a2e 100644 --- a/mssql/base.py +++ b/mssql/base.py @@ -618,11 +618,14 @@ def format_sql(self, sql, params): return sql def format_group_by_params(self, query, params): + # Prepare query for string formatting + query = re.sub(r'%\w+', '{}', query) + if params: # Insert None params directly into the query if None in params: - null_params = ['NULL' if param is None else '%s' for param in params] - query = query % tuple(null_params) + null_params = ['NULL' if param is None else '{}' for param in params] + query = query.format(*null_params) params = tuple(p for p in params if p is not None) params = [(param, type(param)) for param in params] params_dict = {param: '@var%d' % i for i, param in enumerate(set(params))} @@ -634,8 +637,7 @@ def format_group_by_params(self, query, params): datatype = self._as_sql_type(key[1], key[0]) variables.append("%s %s = %%s " % (value, datatype)) params.append(key[0]) - query = ('DECLARE %s \n' % ','.join(variables)) + (query % tuple(args)) - + query = ('DECLARE %s \n' % ','.join(variables)) + (query.format(*args)) return query, params def format_params(self, params): diff --git a/mssql/introspection.py b/mssql/introspection.py index 1f21720..96974ec 100644 --- a/mssql/introspection.py +++ b/mssql/introspection.py @@ -77,19 +77,22 @@ def get_table_list(self, cursor): """ Returns a list of table and view names in the current database. """ - sql = """SELECT - TABLE_NAME, - TABLE_TYPE, - CAST(ep.value AS VARCHAR) AS COMMENT - FROM INFORMATION_SCHEMA.TABLES i - LEFT JOIN sys.tables t ON t.name = i.TABLE_NAME - LEFT JOIN sys.extended_properties ep ON t.object_id = ep.major_id - AND ((ep.name = 'MS_DESCRIPTION' AND ep.minor_id = 0) OR ep.value IS NULL) - AND i.TABLE_SCHEMA = %s""" % ( - get_schema_name()) + if VERSION >= (4, 2) and self.connection.features.supports_comments: + sql = """SELECT + TABLE_NAME, + TABLE_TYPE, + CAST(ep.value AS VARCHAR) AS COMMENT + FROM INFORMATION_SCHEMA.TABLES i + LEFT JOIN sys.tables t ON t.name = i.TABLE_NAME + LEFT JOIN sys.extended_properties ep ON t.object_id = ep.major_id + AND ((ep.name = 'MS_DESCRIPTION' AND ep.minor_id = 0) OR ep.value IS NULL) + WHERE i.TABLE_SCHEMA = %s""" % ( + get_schema_name()) + else: + sql = 'SELECT TABLE_NAME, TABLE_TYPE FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = %s' % (get_schema_name()) cursor.execute(sql) types = {'BASE TABLE': 't', 'VIEW': 'v'} - if VERSION >= (4, 2): + if VERSION >= (4, 2) and self.connection.features.supports_comments: return [TableInfo(row[0], types.get(row[1]), row[2]) for row in cursor.fetchall() if row[0] not in self.ignored_tables] @@ -145,7 +148,7 @@ def get_table_description(self, cursor, table_name, identity_check=True): column.append(collation_name[0] if collation_name else '') else: column.append('') - if VERSION >= (4, 2): + if VERSION >= (4, 2) and self.connection.features.supports_comments: sql = """select CAST(ep.value AS VARCHAR) AS COMMENT FROM sys.columns c INNER JOIN sys.tables t ON c.object_id = t.object_id @@ -174,8 +177,7 @@ def get_table_description(self, cursor, table_name, identity_check=True): start += 1 end -= 1 column[7] = default_value[start:end + 1] - - if VERSION >= (4, 2): + if VERSION >= (4, 2) and self.connection.features.supports_comments: items.append(FieldInfo(*column)) else: items.append(BaseFieldInfo(*column)) diff --git a/mssql/schema.py b/mssql/schema.py index 034ca64..0eff3d9 100644 --- a/mssql/schema.py +++ b/mssql/schema.py @@ -388,9 +388,10 @@ def _column_generated_sql(self, field): """Return the SQL to use in a GENERATED ALWAYS clause.""" expression_sql, params = field.generated_sql(self.connection) persistency_sql = "PERSISTED" if field.db_persist else "" - if params: + if self.connection.features.requires_literal_defaults: expression_sql = expression_sql % tuple(self.quote_value(p) for p in params) - return f"AS {expression_sql} {persistency_sql}" + params = () + return f"GENERATED ALWAYS AS ({expression_sql}) {persistency_sql}", params def _alter_field(self, model, old_field, new_field, old_type, new_type, old_db_params, new_db_params, strict=False): @@ -1024,6 +1025,8 @@ def add_field(self, model, field): # It might not actually have a column behind it if definition is None: return + if col_type_suffix := field.db_type_suffix(connection=self.connection): + definition += f" {col_type_suffix}" # Remove column type from definition if field is generated if (django_version >= (5,0) and field.generated): definition = definition[definition.find('AS'):] diff --git a/setup.py b/setup.py index 6f5f26e..0c9a712 100644 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ setup( name='mssql-django', - version='1.4.2', + version='1.5', description='Django backend for Microsoft SQL Server', long_description=long_description, long_description_content_type='text/markdown', diff --git a/testapp/settings.py b/testapp/settings.py index 2fe5aad..e7ee9cb 100644 --- a/testapp/settings.py +++ b/testapp/settings.py @@ -266,7 +266,6 @@ 'db_functions.datetime.test_extract_trunc.DateFunctionWithTimeZoneTests.test_extract_lookup_name_sql_injection', 'db_functions.datetime.test_extract_trunc.DateFunctionTests.test_extract_lookup_name_sql_injection', 'schema.tests.SchemaTests.test_autofield_to_o2o', - 'schema.tests.SchemaTests.test_add_auto_field', 'prefetch_related.tests.PrefetchRelatedTests.test_m2m_prefetching_iterator_with_chunks', 'migrations.test_operations.OperationTests.test_create_model_with_boolean_expression_in_check_constraint', 'queries.test_qs_combinators.QuerySetSetOperationTests.test_union_in_subquery_related_outerref',