@@ -1076,8 +1076,6 @@ def visit_empty_set_expr(self, element_types):
10761076
10771077class SQLiteDDLCompiler (compiler .DDLCompiler ):
10781078 def get_column_specification (self , column , ** kwargs ):
1079- if column .computed is not None :
1080- raise exc .CompileError ("SQLite does not support computed columns" )
10811079
10821080 coltype = self .dialect .type_compiler .process (
10831081 column .type , type_expression = column
@@ -1124,6 +1122,9 @@ def get_column_specification(self, column, **kwargs):
11241122
11251123 colspec += " AUTOINCREMENT"
11261124
1125+ if column .computed is not None :
1126+ colspec += " " + self .process (column .computed )
1127+
11271128 return colspec
11281129
11291130 def visit_primary_key_constraint (self , constraint ):
@@ -1690,41 +1691,93 @@ def get_view_definition(self, connection, view_name, schema=None, **kw):
16901691
16911692 @reflection .cache
16921693 def get_columns (self , connection , table_name , schema = None , ** kw ):
1694+ pragma = "table_info"
1695+ # computed columns are threaded as hidden, they require table_xinfo
1696+ if self .server_version_info >= (3 , 31 ):
1697+ pragma = "table_xinfo"
16931698 info = self ._get_table_pragma (
1694- connection , "table_info" , table_name , schema = schema
1699+ connection , pragma , table_name , schema = schema
16951700 )
1696-
16971701 columns = []
1702+ tablesql = None
16981703 for row in info :
1699- (name , type_ , nullable , default , primary_key ) = (
1700- row [1 ],
1701- row [2 ].upper (),
1702- not row [3 ],
1703- row [4 ],
1704- row [5 ],
1705- )
1704+ name = row [1 ]
1705+ type_ = row [2 ].upper ()
1706+ nullable = not row [3 ]
1707+ default = row [4 ]
1708+ primary_key = row [5 ]
1709+ hidden = row [6 ] if pragma == "table_xinfo" else 0
1710+
1711+ # hidden has value 0 for normal columns, 1 for hidden columns,
1712+ # 2 for computed virtual columns and 3 for computed stored columns
1713+ # https://www.sqlite.org/src/info/069351b85f9a706f60d3e98fbc8aaf40c374356b967c0464aede30ead3d9d18b
1714+ if hidden == 1 :
1715+ continue
1716+
1717+ generated = bool (hidden )
1718+ persisted = hidden == 3
1719+
1720+ if tablesql is None and generated :
1721+ tablesql = self ._get_table_sql (
1722+ connection , table_name , schema , ** kw
1723+ )
17061724
17071725 columns .append (
17081726 self ._get_column_info (
1709- name , type_ , nullable , default , primary_key
1727+ name ,
1728+ type_ ,
1729+ nullable ,
1730+ default ,
1731+ primary_key ,
1732+ generated ,
1733+ persisted ,
1734+ tablesql ,
17101735 )
17111736 )
17121737 return columns
17131738
1714- def _get_column_info (self , name , type_ , nullable , default , primary_key ):
1739+ def _get_column_info (
1740+ self ,
1741+ name ,
1742+ type_ ,
1743+ nullable ,
1744+ default ,
1745+ primary_key ,
1746+ generated ,
1747+ persisted ,
1748+ tablesql ,
1749+ ):
1750+
1751+ if generated :
1752+ # the type of a column "cc INTEGER GENERATED ALWAYS AS (1 + 42)"
1753+ # somehow is "INTEGER GENERATED ALWAYS"
1754+ type_ = re .sub ("generated" , "" , type_ , flags = re .IGNORECASE )
1755+ type_ = re .sub ("always" , "" , type_ , flags = re .IGNORECASE ).strip ()
1756+
17151757 coltype = self ._resolve_type_affinity (type_ )
17161758
17171759 if default is not None :
17181760 default = util .text_type (default )
17191761
1720- return {
1762+ colspec = {
17211763 "name" : name ,
17221764 "type" : coltype ,
17231765 "nullable" : nullable ,
17241766 "default" : default ,
17251767 "autoincrement" : "auto" ,
17261768 "primary_key" : primary_key ,
17271769 }
1770+ if generated :
1771+ sqltext = ""
1772+ if tablesql :
1773+ pattern = r"[^,]*\s+AS\s+\(([^,]*)\)\s*(?:virtual|stored)?"
1774+ match = re .search (
1775+ re .escape (name ) + pattern , tablesql , re .IGNORECASE
1776+ )
1777+ if match :
1778+ sqltext = match .group (1 )
1779+ colspec ["computed" ] = {"sqltext" : sqltext , "persisted" : persisted }
1780+ return colspec
17281781
17291782 def _resolve_type_affinity (self , type_ ):
17301783 """Return a data type from a reflected column, using affinity tules.
0 commit comments