Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python 

2# cardinal_pythonlib/sqlalchemy/engine_func.py 

3 

4""" 

5=============================================================================== 

6 

7 Original code copyright (C) 2009-2021 Rudolf Cardinal (rudolf@pobox.com). 

8 

9 This file is part of cardinal_pythonlib. 

10 

11 Licensed under the Apache License, Version 2.0 (the "License"); 

12 you may not use this file except in compliance with the License. 

13 You may obtain a copy of the License at 

14 

15 https://www.apache.org/licenses/LICENSE-2.0 

16 

17 Unless required by applicable law or agreed to in writing, software 

18 distributed under the License is distributed on an "AS IS" BASIS, 

19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

20 See the License for the specific language governing permissions and 

21 limitations under the License. 

22 

23=============================================================================== 

24 

25**Functions to help with SQLAlchemy Engines.** 

26 

27""" 

28 

29from typing import Tuple, TYPE_CHECKING 

30 

31from cardinal_pythonlib.sqlalchemy.dialect import ( 

32 get_dialect_name, 

33 SqlaDialectName, 

34) 

35 

36if TYPE_CHECKING: 

37 from sqlalchemy.engine.base import Engine 

38 from sqlalchemy.engine.result import ResultProxy 

39 

40 

41# ============================================================================= 

42# Helper functions for SQL Server 

43# ============================================================================= 

44 

45def is_sqlserver(engine: "Engine") -> bool: 

46 """ 

47 Is the SQLAlchemy :class:`Engine` a Microsoft SQL Server database? 

48 """ 

49 dialect_name = get_dialect_name(engine) 

50 return dialect_name == SqlaDialectName.SQLSERVER 

51 

52 

53def get_sqlserver_product_version(engine: "Engine") -> Tuple[int]: 

54 """ 

55 Gets SQL Server version information. 

56 

57 Attempted to use ``dialect.server_version_info``: 

58 

59 .. code-block:: python 

60 

61 from sqlalchemy import create_engine 

62 

63 url = "mssql+pyodbc://USER:PASSWORD@ODBC_NAME" 

64 engine = create_engine(url) 

65 dialect = engine.dialect 

66 vi = dialect.server_version_info 

67 

68 Unfortunately, ``vi == ()`` for an SQL Server 2014 instance via 

69 ``mssql+pyodbc``. It's also ``None`` for a ``mysql+pymysql`` connection. So 

70 this seems ``server_version_info`` is a badly supported feature. 

71 

72 So the only other way is to ask the database directly. The problem is that 

73 this requires an :class:`Engine` or similar. (The initial hope was to be 

74 able to use this from within SQL compilation hooks, to vary the SQL based 

75 on the engine version. Still, this isn't so bad.) 

76 

77 We could use either 

78 

79 .. code-block:: sql 

80 

81 SELECT @@version; -- returns a human-readable string 

82 SELECT SERVERPROPERTY('ProductVersion'); -- better 

83 

84 The ``pyodbc`` interface will fall over with ``ODBC SQL type -150 is not 

85 yet supported`` with that last call, though, meaning that a ``VARIANT`` is 

86 coming back, so we ``CAST`` as per the source below. 

87 """ 

88 assert is_sqlserver(engine), ( 

89 "Only call get_sqlserver_product_version() for Microsoft SQL Server " 

90 "instances." 

91 ) 

92 sql = "SELECT CAST(SERVERPROPERTY('ProductVersion') AS VARCHAR)" 

93 rp = engine.execute(sql) # type: ResultProxy 

94 row = rp.fetchone() 

95 dotted_version = row[0] # type: str # e.g. '12.0.5203.0' 

96 return tuple(int(x) for x in dotted_version.split(".")) 

97 

98 

99# https://www.mssqltips.com/sqlservertip/1140/how-to-tell-what-sql-server-version-you-are-running/ # noqa 

100SQLSERVER_MAJOR_VERSION_2000 = 8 

101SQLSERVER_MAJOR_VERSION_2005 = 9 

102SQLSERVER_MAJOR_VERSION_2008 = 10 

103SQLSERVER_MAJOR_VERSION_2012 = 11 

104SQLSERVER_MAJOR_VERSION_2014 = 12 

105SQLSERVER_MAJOR_VERSION_2016 = 13 

106SQLSERVER_MAJOR_VERSION_2017 = 14 

107 

108 

109def is_sqlserver_2008_or_later(engine: "Engine") -> bool: 

110 """ 

111 Is the SQLAlchemy :class:`Engine` an instance of Microsoft SQL Server, 

112 version 2008 or later? 

113 """ 

114 if not is_sqlserver(engine): 

115 return False 

116 version_tuple = get_sqlserver_product_version(engine) 

117 return version_tuple >= (SQLSERVER_MAJOR_VERSION_2008, )