text: # -*- coding: utf-8 -*-
  # Copyright (C) 2013-2020 Mag. Christian Tanzer All rights reserved
  # Glasauergasse 32, A--1130 Wien, Austria. tanzer@swing.co.at
  # #*** <License> ************************************************************#
  # This module is part of the package MOM.DBW.SAW.
  #
  # This module is licensed under the terms of the BSD 3-Clause License
  # <http://www.c-tanzer.at/license/bsd_3c.html>.
  # #*** </License> ***********************************************************#
  #
  #++
  # Name
  #    MOM.DBW.SAW.E_Type_Wrapper
  #
  # Purpose
  #    SAW specific information about a E_Type
  #
  # Revision Dates
  #    24-Jun-2013 (CT) Creation
  #     1-Jul-2013 (CT) Add delete/insert/select/update, children/descendents
  #     2-Jul-2013 (CT) Add `insert_cargo`, `reconstruct`, `reload`
  #     3-Jul-2013 (CT) Add `Partial_E_Type_Wrapper`, factor `_E_Type_Wrapper_`
  #     3-Jul-2013 (CT) Change `db_attrs_i` to use `.Derived`
  #     3-Jul-2013 (CT) Add `q_able_attrs`
  #     5-Jul-2013 (CT) Factor `sa_tables`
  #     5-Jul-2013 (CT) Add `_Column_Mapper_`, `E_Type_Wrapper.QC`
  #     7-Jul-2013 (CT) Factor `ancestors` to `_E_Type_Wrapper_`
  #     7-Jul-2013 (CT) Add guard for `sa_table` to `ancestors`
  #     8-Jul-2013 (CT) Use `.ATW.SA_Type`
  #     8-Jul-2013 (CT) Add `_Column_Mapper_.__getitem__`
  #    11-Jul-2013 (CT) Add all `q_able_names` to `q_able_attrs`
  #    16-Jul-2013 (CT) Add properties `Reloader`, `sequences`
  #    17-Jul-2013 (CT) Add property `Q_Result`
  #    18-Jul-2013 (CT) Fix `delete`, `insert`, `update`
  #    18-Jul-2013 (CT) Factor & fix `_exec_insert`, `_insert_kw`, `_insert_iter`
  #    20-Jul-2013 (CT) Reverse order of `sa_tables_strict` (--> root first)
  #    20-Jul-2013 (CT) Add `joined`, `joined_strict`
  #    22-Jul-2013 (CT) Add `E_Type_Wrapper_Alias`, `_Column_Mapper_Alias_`
  #    23-Jul-2013 (CT) Add `xtra` to `attr_join_etw_alias`
  #    23-Jul-2013 (CT) Factor `spk_name`
  #    24-Jul-2013 (CT) Add and use `sa_join_restriction`
  #    26-Jul-2013 (CT) Create a table for each non-partial E_Type
  #                     (even if it doesn't have db_attrs_o)
  #    26-Jul-2013 (CT) Change `where` of `select_strict` to `False`
  #    28-Jul-2013 (CT) Use `getattr (entity, self.spk_name)`, not `entity.pid`
  #    30-Jul-2013 (CT) Change `sa_tables_descendents` to yield empty tables of
  #                     types with descendents; otherwise, JOIN for `select` fails
  #     1-Aug-2013 (CT) Use `spk_attr_name` and `spk` of `MOM.Entity`
  #     2-Aug-2013 (CT) Enable `sa_join_restriction` even for `len (tables) == 1`
  #     4-Aug-2013 (CT) Use `_saw_kind_wrapper`, not `_SAW_Wrapper`
  #     5-Aug-2013 (CT) Factor `Q_Result_Type`, add `Q_Result_strict`
  #     8-Aug-2013 (CT) Add guard to newly factored `q_able_attrs_i._gen`
  #     9-Aug-2013 (CT) Change `Map` to use `q_able_attrs`
  #     9-Aug-2013 (CT) Change `parent` to choose a grand-parent if necessary
  #     9-Aug-2013 (CT) Add and use `sa_table_x`
  #     9-Aug-2013 (CT) Change `sa_tables_descendents` to handle partial
  #                     descendents of relevant E_Types
  #     9-Aug-2013 (CT) Simplify `joined` and `joined_strict` to `set (tables)`
  #    13-Aug-2013 (CT) Change `parent` to filter partial ancestors between a
  #                     relevant root and the top-level class defining a sa_table
  #    25-Aug-2013 (CT) Change `insert` to guard `seq.reserve` with
  #                     `session.scope.reserve_surrogates`
  #    26-Aug-2013 (CT) Use `sequence.insert` and `.extract`, not home-grown code
  #    26-Aug-2013 (CT) Change `_setup` to include `seq._table_kw`, if any
  #    28-Aug-2013 (CT) Factor `del_stmt`, `ins_stmt`, `upd_stmt`, `where_spk`,
  #                     `_exec_update`, move MySQL specifics to `SAW.MY`
  #    31-Aug-2013 (CT) Add explicit `alias_name` to `E_Type_Wrapper_Alias`
  #     5-Sep-2013 (CT) Change `attr_join_etw_alias` to include both table
  #                     names into `alias`
  #    24-Sep-2013 (CT) Add optional argument `X_ETW` to `attr_join_etw_alias`
  #    24-Sep-2013 (CT) Change `E_Type_Wrapper_Alias`:
  #                     * change `table_name` to use `.sa_table_x.name`, if any
  #                     * use `_mangled_alias` in `_get_sa_table_alias`
  #    26-Sep-2013 (CT) Add `reset_cache`
  #     2-Sep-2014 (CT) Quote table name if it's not a valid SQL identifier
  #    12-Sep-2014 (CT) Factor `etw_alias`
  #    23-Apr-2015 (CT) Add `pid_query`, `pid_query_stmt`, `type_name_select_stmt`
  #                     + plus `_pid_query_direct`, `_pid_query_indirect`
  #     8-Oct-2015 (CT) Change `__getattr__` to *not* handle `__XXX__`
  #    20-Oct-2016 (CT) Add `iea_type_restrictions` to `E_Type_Wrapper._setup`
  #    20-Oct-2016 (CT) Add `sa_table` guard to `del_stmt`, `ins_stmt`, `upd_stmt`
  #                     `sa_tables_descendents`
  #                     + Add guards for `stmt` to `_delete`, `_exec_insert`,
  #                       `_exec_update`
  #    24-Oct-2016 (CT) Add `iea_type_restriction_map` to `sa_joins_strict`
  #    ««revision-date»»···
  #--
  
  from   _TFL                       import TFL
  from   _TFL.pyk                   import pyk
  
  from   _MOM.import_MOM            import MOM, Q
  from   _MOM._DBW._SAW             import SA
  
  import _MOM._DBW._SAW.Manager
  import _MOM._DBW._SAW.Q_Result
  import _MOM._DBW._SAW.Sequence
  import _MOM.Entity
  
  from   _TFL._Meta.Single_Dispatch import Single_Dispatch_Method
  from   _TFL.predicate             import filtered_join, first, uniq
  
  import _TFL._Meta.Object
  import _TFL._Meta.Once_Property
  import _TFL.Decorator
  
  class _Column_Mapper_Base_ (TFL.Meta.Object) :
  
      def __init__ (self, ETW, prefix = None) :
          self._ETW    = ETW
          self._prefix = prefix
      # end def __init__
  
      @TFL.Meta.Once_Property
      @TFL.getattr_safe
      def Map (self) :
          result = {}
          prefix = self._prefix
          for qaw in pyk.itervalues (self._ETW.q_able_attrs) :
              qaw.update_col_map (self, result, prefix)
          return result
      # end def Map
  
      def __getattr__ (self, name) :
          try :
              return self.Map [name]
          except LookupError :
              raise AttributeError (name)
      # end def __getattr__
  
      def __getitem__ (self, key) :
          return self.Map [key]
      # end def __getitem__
  
      def __repr__ (self) :
          ETW = self._ETW
          return "<%s for %s>" % (self._repr_name, ETW.type_name)
      # end def __repr__
  
  # end class _Column_Mapper_Base_
  
  class _Column_Mapper_ (_Column_Mapper_Base_) :
      """Column map for E_Type_Wrapper"""
  
      _repr_name = "Col-Mapper"
  
  # end class _Column_Mapper_
  
  class _Column_Mapper_Alias_ (_Column_Mapper_Base_) :
  
      _repr_name = "Col-Mapper-Alias"
  
  # end class _Column_Mapper_Alias_
  
  class _E_Type_Wrapper_Base_ (TFL.Meta.Object) :
  
      def __init__ (self) :
          self.QC       = self.QC_Type (self)
          self._aja_map = {}
      # end def __init__
  
      @TFL.Meta.Once_Property
      def db_attrs (self) :
          result = dict (self.db_attrs_i)
          result.update (self.db_attrs_o)
          return result
      # end def db_attrs
  
      @TFL.Meta.Once_Property
      def has_relevant_tables (self) :
          return \
              (   self.root_table is not None
              and (not self.is_partial or self.children)
              )
      # end def has_relevant_tables
  
      @TFL.Meta.Once_Property
      def is_partial (self) :
          return self.e_type.is_partial
      # end def is_partial
  
      @TFL.Meta.Once_Property
      def key_o (self) :
          return self.e_type.own_surrogate
      # end def key_o
  
      @TFL.Meta.Once_Property
      def last_cid_col (self) :
          sa_table = self.sa_table_x
          if sa_table is not None :
              lcw  = self.db_attrs.get  ("last_cid")
              if lcw is not None :
                  return sa_table.c.get (lcw.ckd_name)
      # end def last_cid_col
  
      @TFL.Meta.Once_Property
      def q_able_attrs (self) :
          result = dict (self.q_able_attrs_i)
          result.update (self.q_able_attrs_o)
          for k, v in tuple (pyk.iteritems (result)) :
              for name in v.q_able_names :
                  if name not in result :
                      result [name] = v
          return result
      # end def q_able_attrs
  
      @TFL.Meta.Once_Property
      def sa_join_restriction (self) :
          """Filter for unrelated MOM.Id_Entity rows.
  
             This is necessary for partial E_Types because the joins to
             descendents are all `LEFT OUTER` and thus MOM.Id_Entity rows of
             instances of non-descendents are otherwise included.
          """
          tables = self.sa_tables_descendents or ()
          if self.sa_table is None :
              def _gen (spk_name, root, tables) :
                  root_spk = root.c [spk_name]
                  for t in tables :
                      yield root_spk == t.c [spk_name]
              return SA.expression.or_ \
                  (* _gen (self.spk_name, self.sa_tables_strict [0], tables))
      # end def sa_join_restriction
  
      @TFL.Meta.Once_Property
      def sa_joins (self) :
          """Joins with all ancestor and all descendent tables."""
          if self.has_relevant_tables :
              result = self.sa_joins_strict
              for t in self.sa_tables_descendents :
                  result = result.outerjoin (t)
              return result
      # end def sa_joins
  
      @TFL.Meta.Once_Property
      def sa_joins_strict (self) :
          """Joins of all ancestor tables with `self.sa_table`, if any."""
          if self.has_relevant_tables :
              tables = self.sa_tables_strict
              result = tables [0]
              for t in tables [1:] :
                  result = result.join (t)
              iea_tr_map = self.e_type.iea_type_restriction_map
              if iea_tr_map :
                  for k, tn in sorted (pyk.iteritems (iea_tr_map)) :
                      if k not in self.db_attrs_o :
                          akw = self.db_attrs.get (k)
                          if akw is not None :
                              etw = self.ATW [tn]
                              if etw.sa_table is not None :
                                  ac = akw.columns [0]
                                  result = result.join \
                                      (etw.sa_table, ac == etw.spk_col)
              return result
      # end def sa_joins_strict
  
      @TFL.Meta.Once_Property
      def sa_tables (self) :
          ts = self.sa_tables_strict      or ()
          td = self.sa_tables_descendents or ()
          return ts + td
      # end def sa_tables
  
      @TFL.Meta.Once_Property
      def select (self) :
          """Select statement including children."""
          if self.has_relevant_tables :
              tables = self.sa_tables
              joins  = self.sa_joins
              result = SA.sql.select \
                  (tables, from_obj = (joins, ), use_labels = True)
              jr = self.sa_join_restriction
              if jr is not None :
                  result = result.where (jr)
              return result
      # end def select
  
      @TFL.Meta.Once_Property
      def select_strict (self) :
          """Select statement excluding children."""
          if self.has_relevant_tables :
              if self.e_type.is_partial :
                  spk_col = self.spk_col
                  if spk_col is not None :
                      return SA.sql.select ([spk_col]).where (False)
              else :
                  tn_col = self.type_name_col
                  if tn_col is not None :
                      tables = self.sa_tables_strict
                      joins  = self.sa_joins_strict
                      result = SA.sql.select \
                          (tables, from_obj = (joins, ), use_labels = True)
                      if len (tables) > 1 or self.descendents :
                          result = result.where (tn_col == self.e_type.type_name)
                      return result
      # end def select_strict
  
      @TFL.Meta.Once_Property
      def spk_col (self) :
          sa_table = self.sa_table_x if self.sa_table_x is not None \
              else self.root_table
          if sa_table is not None :
              return sa_table.c [self.spk_name]
      # end def spk_col
  
      @TFL.Meta.Once_Property
      def spk_name (self) :
          return self.e_type.spk_attr_name
      # end def spk_name
  
      @TFL.Meta.Once_Property
      def spk_wrapper (self) :
          return self.db_attrs [self.spk_name]
      # end def spk_wrapper
  
      @TFL.Meta.Once_Property
      def type_name_col (self) :
          tn_wrapper = self.type_name_wrapper
          if tn_wrapper is not None :
              return tn_wrapper.columns [0]
      # end def type_name_col
  
      @TFL.Meta.Once_Property
      def type_name_wrapper (self) :
          return self.db_attrs.get ("type_name")
      # end def type_name_wrapper
  
      def attr_join_etw_alias (self, akw, E_Type, X_ETW = None) :
          ETW    = E_Type._SAW
          a_name = akw.ckd_name
          t_name = ETW.table_name
          middle = (self if X_ETW is None else X_ETW).table_name
          key    = (t_name, middle, a_name)
          xtra   = ()
          result = self.etw_alias (ETW, * key)
          if akw.columns and a_name not in self.sa_table_x.c :
              ### need an extra join because `akw`'s column is not in
              ### `self.sa_table_x` but in that of a parent
              ###     --> join the parent's table, too
              akw_col = akw.columns [0]
              xtra    = (getattr (akw_col.table.c, self.spk_name), self.spk_col)
          return result, xtra
      # end def attr_join_etw_alias
  
      def etw_alias (self, ETW, * key) :
          try :
              result = self._aja_map [key]
          except KeyError :
              alias  = "%s____%s__%s" % key
              result = self._aja_map [key] = E_Type_Wrapper_Alias (ETW, alias)
          return result
      # end def etw_alias
  
      def reset_cache (self) :
          self._aja_map = {}
      # end def reset_cache
  
  # end class _E_Type_Wrapper_Base_
  
  class _E_Type_Wrapper_ (_E_Type_Wrapper_Base_) :
  
      QC_Type             = _Column_Mapper_
      sa_table            = None
      sequence            = None
  
      def __init__ (self, e_type) :
          self.ATW = ATW  = e_type.app_type._SAW
          self.DBW        = ATW.DBW
          self.PNS        = ATW.PNS
          self.e_type     = e_type
          self.type_name  = e_type.type_name
          self.__super.__init__ ()
      # end def __init__
  
      @TFL.Meta.Once_Property
      def ancestors (self) :
          def _gen (self) :
              p = self.parent
              if p :
                  if p.sa_table is not None :
                      yield p
                  yield from p.ancestors
          return tuple (uniq (_gen (self)))
      # end def ancestors
  
      @TFL.Meta.Once_Property
      def ancestors_r (self) :
          return self.ancestors [::-1]
      # end def ancestors_r
  
      @TFL.Meta.Once_Property
      def db_attrs_i (self) :
          p = self.parent
          if p :
              et_attrs = self.e_type.attributes
              return dict \
                  (  (name, pw.Derived (self, et_attrs [name]))
                  for name, pw in pyk.iteritems (p.db_attrs)
                  )
          return {}
      # end def db_attrs_i
  
      @TFL.Meta.Once_Property
      def descendents (self) :
          def _gen (self) :
              for c in self.children :
                  yield c
                  yield from c.descendents
          return tuple (uniq (_gen (self)))
      # end def descendents
  
      @TFL.Meta.Once_Property
      def joined (self) :
          return set (self.sa_tables)
      # end def joined
  
      @TFL.Meta.Once_Property
      def joined_strict (self) :
          return set (self.sa_tables_strict)
      # end def joined_strict
  
      @TFL.Meta.Once_Property
      def key_p (self) :
          ancestors = self.ancestors
          if ancestors :
              return ancestors [-1].key_o
      # end def key_p
  
      @TFL.Meta.Once_Property
      def pid_query (self) :
          if self.e_type.is_partial :
              return self._pid_query_indirect
          else :
              return self._pid_query_direct
      # end def pid_query
  
      @TFL.Meta.Once_Property
      def pid_query_stmt (self) :
          return self.Q_Result.filter (Q.pid == Q.BVAR.pid).limit (1)
      # end def pid_query_stmt
  
      @TFL.Meta.Once_Property
      def type_name_select_stmt (self) :
          pid_col = self.root_table.c.pid
          tn_col  = self.type_name_col
          return SA.sql.select \
              ([tn_col]).where (pid_col == SA.sql.bindparam ("pid")).limit (1)
      # end def type_name_select_stmt
  
      @TFL.Meta.Once_Property
      def q_able_attrs_i (self) :
          p = self.parent
          if p :
              et_attrs = self.e_type.attributes
              def _gen (self, p, et_attrs) :
                  for k, pw in pyk.iteritems (p.q_able_attrs) :
                      ak = et_attrs.get (pw.name)
                      if ak is not None :
                          yield k, pw.Derived (self, ak)
              return dict (_gen (self, p, et_attrs))
          return {}
      # end def q_able_attrs_i
  
      @TFL.Meta.Once_Property
      def q_able_attrs_o (self) :
          DBW    = self.DBW
          e_type = self.e_type
          ignore = set  (self.q_able_attrs_i)
          result = dict (self.db_attrs_o)
          ignore.update (result)
          result.update \
              (  (a.name, a._saw_kind_wrapper_pq (DBW, self))
              for a in e_type.q_able if a.name not in ignore
              )
          return result
      # end def q_able_attrs_o
  
      @TFL.Meta.Once_Property
      def Q_Result (self) :
          return self.Q_Result_Type (self.e_type, _strict = False)
      # end def Q_Result
  
      @TFL.Meta.Once_Property
      def Q_Result_strict (self) :
          return self.Q_Result_Type (self.e_type, _strict = True)
      # end def Q_Result_strict
  
      @TFL.Meta.Once_Property
      def Q_Result_Type (self) :
          return getattr (self.PNS.Q_Result, self.e_type._SAW_Q_Result.__name__)
      # end def Q_Result_Type
  
      @TFL.Meta.Once_Property
      def Reloader (self) :
          return self.PNS.Q_Result.E_Type_Reload (self.e_type)
      # end def Reloader
  
      @TFL.Meta.Once_Property
      def root_table (self) :
          ancestors = self.ancestors
          if ancestors :
              return ancestors [-1].sa_table
          return self.sa_table
      # end def root_table
  
      @TFL.Meta.Once_Property
      def sa_tables_descendents (self) :
          """SA tables of all descendents."""
          if self.root_table is not None :
              def _gen (self) :
                  is_pr = self.sa_table is None and self.e_type.is_relevant
                  for d in self.descendents :
                      if (is_pr or d.db_attrs_o or d.descendents) \
                             and d.sa_table is not None :
                          ### don't yield empty tables
                          yield d.sa_table
              return tuple (_gen (self))
      # end def sa_tables_descendents
  
      @TFL.Meta.Once_Property
      def sa_tables_strict (self) :
          """SA Tables excluding children."""
          if self.root_table is not None:
              def _gen (self) :
                  for a in self.ancestors_r :
                      yield a.sa_table
                  if self.sa_table is not None :
                      yield self.sa_table
              return tuple (_gen (self))
      # end def sa_tables_strict
  
      @TFL.Meta.Once_Property
      def sequences (self) :
          def _gen (self) :
              for a in self.ancestors_r :
                  if a.sequence :
                      yield a.sequence
              if self.sequence :
                  yield self.sequence
          return tuple (_gen (self))
      # end def sequences
  
      @TFL.Meta.Once_Property
      def unique (self) :
          result = set  (self.unique_i)
          result.update (self.unique_o)
          return tuple (sorted (result))
      # end def unique
  
      @TFL.Meta.Once_Property
      def unique_i (self) :
          p = self.parent
          if p :
              return p.unique
          return ()
      # end def unique
  
      def _pid_query_direct (self, session, pid) :
          result = first (self.pid_query_stmt.bind (session, pid = pid))
          return result
      # end def _pid_query_direct
  
      def _pid_query_indirect (self, session, pid) :
          pqs = self.pid_query_stmt
          tnq = session.connection.execute (self.type_name_select_stmt, pid = pid)
          tn  = first (tnq) [0]
          ETW = session.scope.entity_type (tn)._SAW
          return ETW._pid_query_direct (session, pid)
      # end def _pid_query_indirect
  
      def __repr__ (self) :
          tns = list (str (t) for t in reversed (self.sa_tables_strict or ()))
          return "<SAW : %s [%s]>" % (self.type_name, " : ".join (tns))
      # end def __repr__
  
  # end class _E_Type_Wrapper_
  
  class E_Type_Wrapper (_E_Type_Wrapper_) :
      """SAW specific information about a E_Type"""
  
      def __init__ (self, e_type) :
          self.__super.__init__ (e_type)
          self.table_name         = e_type._saw_table_name (self.ATW.DBW)
          self.children           = []
          self.fk_cols            = []
          self._sa_alias_name_map = {}
          self._setup (e_type)
      # end def __init__
  
      @TFL.Meta.Once_Property
      def db_attrs_o (self) :
          inherited = self.db_attrs_i
          result    = dict \
              (  (a.name, a._saw_kind_wrapper (self.DBW, self))
              for a in self.e_type.db_attr
              if  a.name not in inherited
              )
          return result
      # end def db_attrs_o
  
      @TFL.Meta.Once_Property
      def del_stmt (self) :
          if self.sa_table is not None :
              return self.sa_table.delete ().where (self.where_spk)
      # end def del_stmt
  
      @TFL.Meta.Once_Property
      def ins_stmt (self) :
          if self.sa_table is not None :
              return self.sa_table.insert ()
      # end def ins_stmt
  
      @TFL.Meta.Once_Property
      def parent (self) :
          e_type  = self.e_type
          sk      = lambda x : bool (getattr (x, "relevant_root", None))
          parents = list \
              ( p for p in sorted (e_type.parents, key = sk, reverse = True)
                if getattr (p, "_SAW", None) and p._SAW.sa_table is not None
              )
          if not parents :
              rr = e_type.relevant_root
              if rr :
                  if rr is e_type :
                      ### there might be partial classes between `e_type` and
                      ### a top-level class with a table
                      ### --> look for that one in all ancestors
                      ancestors = list \
                          ( p for p in sorted
                              (e_type.ancestors, key = sk, reverse = True)
                            if  getattr (p, "_SAW", None)
                            and p._SAW.sa_table is not None
                          )
                      parents = list \
                          (a for a in ancestors if a._SAW.e_type is a) [:1]
                  else :
                      ### there are partial classes between `e_type` and
                      ### `e_type.relevant_root` that don't have `_SAW`
                      ### --> select `e_type.relevant_root` as `parent` even
                      ###     though it's a grand-parent or greater-grand-parent
                      parents = [e_type.relevant_root]
          if len (parents) > 1 :
              np_parents = list (p for p in parents if p.is_relevant)
              if len (np_parents) > 1 :
                  raise NotImplementedError \
                      ( "Multiple inheritance currently not supported\n"
                        "%s, %s"
                      % (e_type.type_name, parents)
                      )
          if parents and parents [0] :
              return  parents [0]._SAW
      # end def parents
  
      @TFL.Meta.Once_Property
      def sa_table_x (self) :
          result = self.sa_table
          if result is None and self.e_type.is_relevant :
              result = self.parent.sa_table
          return result
      # end def sa_table_x
  
      @TFL.Meta.Once_Property
      def upd_stmt (self) :
          if self.sa_table is not None :
              return self.sa_table.update ().where (self.where_spk)
      # end def upd_stmt
  
      @TFL.Meta.Once_Property
      def where_spk (self) :
          return self.spk_col == SA.sql.bindparam ("spk")
      # end def where_spk
  
      def delete (self, session, entity) :
          self._delete (session, entity)
          for a in self.ancestors :
              a._delete (session, entity)
      # end def delete
  
      def insert (self, session, entity, ** spks) :
          kw = {}
          for a in self.ancestors_r :
              a._insert (session, entity, spks, kw)
          self._insert  (session, entity, spks, kw)
          result = kw [self.spk_name]
          return result
      # end def insert
  
      def insert_cargo (self, session, pid, pickle_cargo) :
          kw  = { self.spk_name : pid }
          for a in self.ancestors_r :
              a._insert_cargo (session, pickle_cargo, kw)
          self._insert_cargo (session, pickle_cargo, kw)
      # end def insert_cargo
  
      def reconstruct (self, session, row) :
          scope        = session.scope
          pickle_cargo = self.row_as_pickle_cargo (row)
          entity       = self.e_type.from_attr_pickle_cargo (scope, pickle_cargo)
          if self.last_cid_col is not None :
              entity._reloaded_last_cid = entity.last_cid
          entity._finish__init__ ()
          return entity
      # end def reconstruct
  
      def reload (self, entity, row) :
          pickle_cargo = self.row_as_pickle_cargo (row)
          entity.reload_from_pickle_cargo (pickle_cargo)
          if self.last_cid_col is not None :
              entity._reloaded_last_cid = entity.last_cid
          return entity
      # end def reload
  
      def reset_cache (self) :
          self._sa_alias_name_map = {}
          self.__super.reset_cache ()
      # end def reset_cache
  
      def row_as_pickle_cargo (self, row, db_attrs = None) :
          result = TFL.defaultdict (tuple)
          if db_attrs is None :
              db_attrs = self.db_attrs
          for name, kind_wrapper in pyk.iteritems (db_attrs) :
              result [name] = kind_wrapper.row_as_pickle_cargo (row)
          return result
      # end def row_as_pickle_cargo
  
      def update (self, session, entity, attrs = ()) :
          if not attrs :
              attrs = self.db_attrs
          for a in self.ancestors_r :
              a._update (session, entity, attrs)
          self._update (session, entity, attrs)
      # end def update
  
      def _delete (self, session, entity) :
          stmt = self.del_stmt
          if stmt is not None :
              session.connection.execute (stmt, spk = entity.spk)
      # end def _delete
  
      def _exec_insert (self, session, values) :
          stmt = self.ins_stmt
          if stmt is not None :
              return session.connection.execute (stmt.values (values))
      # end def _exec_insert
  
      def _exec_update (self, session, entity, values, ** xkw) :
          stmt = self.upd_stmt
          if stmt is not None :
              return session.connection.execute (stmt.values (values), ** xkw)
      # end def _exec_update
  
      def _insert (self, session, entity, spks, kw) :
          seq = self.sequence
          if seq :
              seq.insert (session, entity, spks, kw)
          ikw = self._insert_kw (session, kw)
          for name, kind_wrapper in self._insert_iter (self.db_attrs_o, ikw) :
              ikw.update (kind_wrapper.col_values (entity))
          result = self._exec_insert (session, ikw)
          if seq :
              seq.extract (session, entity, kw, result)
          return result
      # end def _insert
  
      def _insert_cargo (self, session, pickle_cargo, kw) :
          ikw = self._insert_kw (session, kw)
          for name, kind_wrapper in self._insert_iter (self.db_attrs_o, ikw) :
              attr_pc = pickle_cargo.get (name)
              if attr_pc is not None :
                  ikw.update (kind_wrapper.col_values_from_cargo (attr_pc))
          return self._exec_insert (session, ikw)
      # end def _insert_cargo
  
      def _insert_kw (self, session, kw) :
          db_attrs_o = self.db_attrs_o
          spk_name   = self.spk_name
          ikw        = dict \
               ( (k, v) for k, v in pyk.iteritems (kw)
               if k == spk_name or k in db_attrs_o
               )
          return ikw
      # end def _insert_kw
  
      def _insert_iter (self, db_attrs_o, ikw) :
          for name, kind_wrapper in pyk.iteritems (db_attrs_o) :
              if name not in ikw :
                  yield name, kind_wrapper
      # end def _insert_iter
  
      def _mangled_alias (self, alias) :
          map = self._sa_alias_name_map
          try :
              result = map [alias]
          except KeyError :
              result = map [alias] = "%s__%d" % (self.table_name, len (map) + 1)
          return result
      # end def _mangled_alias
  
      def _setup (self, e_type) :
          db_attrs_o = self.db_attrs_o
          if db_attrs_o or \
                 not (e_type.is_partial or e_type.iea_type_restrictions) :
              ATW           = self.ATW
              PNS           = self.PNS
              cols, unique  = self._setup_columns (e_type, db_attrs_o, PNS)
              t_name        = self.table_name
              if self.key_o or self.key_p :
                  kw  = dict (PNS.DBS.table_kw)
                  if ATW.no_identifier_pat.search (t_name) :
                      kw ["quote"] = True
                  seq = self.sequence
                  if seq and seq.sa_column is None :
                      kw.update (seq._table_kw)
                  self.sa_table = sa_table = SA.schema.Table \
                      (t_name, ATW.metadata,  * cols, ** kw)
                  sa_table.MOM_Wrapper = self
              self.unique_o = tuple (sorted (unique))
          parent = self.parent
          if parent :
              parent.children.append (self)
      # end def _setup
  
      def _setup_columns (self, e_type, db_attrs_o, PNS) :
          result  = []
          unique  = []
          key_o   = self.key_o
          key_p   = self.key_p
          if key_o :
              seq = self.sequence = key_o.attr._saw_sequence (self)
          if key_p :
              spk_name  = self.spk_name
              key_p_col = SA.schema.Column \
                  ( spk_name
                  , self.ATW.SA_Type.sized_int_type (key_p.Pickled_Type)
                  , SA.schema.ForeignKey (self.parent.sa_table.c [spk_name])
                  , primary_key = not key_o
                  , unique      = True
                  )
              key_p_col.MOM_Kind    = key_p
              key_p_col.MOM_Wrapper = None
              result.append (key_p_col)
          for name, kind_wrapper in pyk.iteritems (db_attrs_o) :
              result.extend (kind_wrapper.columns)
              unique.extend (kind_wrapper.unique)
          return result, unique
      # end def _setup_columns
  
      def _update (self, session, entity, attrs) :
          values = {}
          for name, kind_wrapper in pyk.iteritems (self.db_attrs_o) :
              if name in attrs and not kind_wrapper.is_surrogate :
                  ### surrogate attributes can't be updated
                  ### even if the surrogate value is the same, `update` raises
                  ### SA.Exception.IntegrityError
                  values.update (kind_wrapper.col_values (entity))
          if values :
              result = self._exec_update \
                  (session, entity, values, spk = entity.spk)
              if not result.rowcount :
                  session.scope.rollback          ()
                  raise MOM.Error.Commit_Conflict ()
      # end def _update
  
  # end class E_Type_Wrapper
  
  class E_Type_Wrapper_Alias (_E_Type_Wrapper_Base_) :
      """Alias for tables of an E_Type_Wrapper"""
  
      QC_Type                 = _Column_Mapper_Alias_
  
      def __init__ (self, ETW, alias_name) :
          self._ETW           = ETW
          self._alias_name    = alias_name
          self._sa_table_map  = {}
          self.__super.__init__ ()
      # end def __init__
  
      @TFL.Meta.Once_Property
      def db_attrs_i (self) :
          return dict \
              (  (k, v.alias (self))
              for k, v in pyk.iteritems (self._ETW.db_attrs_i)
              )
      # end def db_attrs_i
  
      @TFL.Meta.Once_Property
      def db_attrs_o (self) :
          return dict \
              (  (k, v.alias (self))
              for k, v in pyk.iteritems (self._ETW.db_attrs_o)
              )
      # end def db_attrs_o
  
      @TFL.Meta.Once_Property
      def e_type (self) :
          return self._ETW.e_type
      # end def e_type
  
      @TFL.Meta.Once_Property
      def q_able_attrs_i (self) :
          return dict \
              (  (k, v.alias (self))
              for k, v in pyk.iteritems (self._ETW.q_able_attrs_i)
              )
      # end def q_able_attrs_i
  
      @TFL.Meta.Once_Property
      def q_able_attrs_o (self) :
          return dict \
              (  (k, v.alias (self))
              for k, v in pyk.iteritems (self._ETW.q_able_attrs_o)
              )
      # end def q_able_attrs_o
  
      @TFL.Meta.Once_Property
      def root_table (self) :
          table = self._ETW.root_table
          if table is not None :
              return self._get_sa_table_alias (table)
      # end def root_table
  
      @TFL.Meta.Once_Property
      def sa_table (self) :
          table = self._ETW.sa_table
          if table is not None :
              return self._get_sa_table_alias (table)
      # end def sa_table
  
      @TFL.Meta.Once_Property
      def sa_table_x (self) :
          table = self._ETW.sa_table_x
          if table is not None :
              return self._get_sa_table_alias (table)
      # end def sa_table_x
  
      @TFL.Meta.Once_Property
      def sa_tables_descendents (self) :
          tables = self._ETW.sa_tables_descendents
          if tables is not None :
              return tuple (self._get_sa_table_alias (t) for t in tables)
      # end def sa_tables_descendents
  
      @TFL.Meta.Once_Property
      def sa_tables_strict (self) :
          tables = self._ETW.sa_tables_strict
          if tables is not None :
              return tuple (self._get_sa_table_alias (t) for t in tables)
      # end def sa_tables_strict
  
      @TFL.Meta.Once_Property
      def table_name (self) :
          table = self.sa_table_x
          if table is not None :
              result = table.name
          else :
              table  = self._ETW.sa_table_x
              result = table.name if table is not None else \
                  self.e_type._saw_table_name (self.ATW.DBW)
          return result
      # end def table_name
  
      def _get_sa_col_alias (self, col) :
          table  = self._get_sa_table_alias (col.table)
          result = table.c [col.name]
          return result
      # end def _get_sa_col_alias
  
      def _get_sa_table_alias (self, table) :
          ETW   = table.MOM_Wrapper
          name  = table.name
          alias = self._alias_name
          if self._ETW.e_type != table.MOM_Wrapper.e_type :
              alias = "%s____%s" % (name, self._alias_name)
          alias  = ETW._mangled_alias (alias)
          map    = self._sa_table_map
          result = map.get (alias)
          if result is None :
              result = map [alias] = table.alias (alias)
              result.MOM_Wrapper   = self
          return result
      # end def _get_sa_table
  
      def _mangled_alias (self, alias) :
          ### `self.e_type._SAW` is the outermost, i.e., un-aliased, ETW
          return self.e_type._SAW._mangled_alias (alias)
      # end def _mangled_alias
  
      def __getattr__ (self, name) :
          if name.startswith ("__") and name.endswith ("__") :
              ### Placate inspect.unwrap of Python 3.5,
              ### which accesses `__wrapped__` and eventually throws `ValueError`
              return getattr (self.__super, name)
          return getattr (self._ETW, name)
      # end def __getattr__
  
      def __repr__ (self) :
          tns = list \
              (str (t.element) for t in reversed (self.sa_tables_strict or ()))
          return "<SAW Alias : %s [%s]>" % (self.type_name, " : ".join (tns))
      # end def __repr__
  
  # end class E_Type_Wrapper_Alias
  
  class Partial_E_Type_Wrapper (_E_Type_Wrapper_) :
      """SAW specific information about a partial E_Type without its own table"""
  
      def __init__ (self, e_type, parent) :
          self.__super.__init__ (e_type)
          self.p_parent = parent
          self.parent   = \
              (    parent
              if   parent.sa_table is not None
              else first (p for p in parent.ancestors if p.sa_table is not None)
              )
      # end def __init__
  
      @TFL.Meta.Once_Property
      def children (self) :
          e_type = self.e_type
          return tuple \
              (c for c in self.p_parent.children if issubclass (c.e_type, e_type))
      # end def children
  
      @TFL.Meta.Once_Property
      def db_attrs_o (self) :
          return {}
      # end def db_attrs_o
  
      @TFL.Meta.Once_Property
      def sa_table_x (self) :
          return self.parent.sa_table
      # end def sa_table_x
  
      @TFL.Meta.Once_Property
      def table_name (self) :
          return self.e_type._saw_table_name (self.ATW.DBW)
      # end def table_name
  
      @TFL.Meta.Once_Property
      def unique_o (self) :
          return ()
      # end def unique_o
  
  # end class Partial_E_Type_Wrapper
  
  if __name__ != "__main__" :
      MOM.DBW.SAW._Export ("*")
  ### __END__ MOM.DBW.SAW.E_Type_Wrapper
  