text: import re
  
  import sqlparse
  from sqlparse.tokens import Whitespace, Comment, Keyword, Name, Punctuation
  
  table_def_regex = re.compile(r'^TABLE\s*\((.+)\)$', re.IGNORECASE)
  
  
  class FunctionMetadata(object):
      def __init__(self, schema_name, func_name, arg_list, return_type,
                   is_aggregate, is_window, is_set_returning):
          """Class for describing a postgresql function"""
  
          self.schema_name = schema_name
          self.func_name = func_name
          self.arg_list = arg_list.strip()
          self.return_type = return_type.strip()
          self.is_aggregate = is_aggregate
          self.is_window = is_window
          self.is_set_returning = is_set_returning
  
      def __eq__(self, other):
          return (isinstance(other, self.__class__) and
                  self.__dict__ == other.__dict__)
  
      def __ne__(self, other):
          return not self.__eq__(other)
  
      def __hash__(self):
          return hash((self.schema_name, self.func_name, self.arg_list,
                       self.return_type, self.is_aggregate, self.is_window,
                       self.is_set_returning))
  
      def __repr__(self):
          return (
              ('%s(schema_name=%r, func_name=%r, arg_list=%r, return_type=%r,'
               ' is_aggregate=%r, is_window=%r, is_set_returning=%r)')
              % (self.__class__.__name__, self.schema_name, self.func_name,
                 self.arg_list, self.return_type, self.is_aggregate,
                 self.is_window, self.is_set_returning)
          )
  
      def fieldnames(self):
          """Returns a list of output field names"""
  
          if self.return_type.lower() == 'void':
              return []
  
          match = table_def_regex.match(self.return_type)
          if match:
              # Function returns a table -- get the column names
              return list(field_names(match.group(1), mode_filter=None))
  
          # Function may have named output arguments -- find them and return
          # their names
          return list(field_names(self.arg_list, mode_filter=('OUT', 'INOUT')))
  
  
  class TypedFieldMetadata(object):
      """Describes typed field from a function signature or table definition
  
          Attributes are:
              name        The name of the argument/column
              mode        'IN', 'OUT', 'INOUT', 'VARIADIC'
              type        A list of tokens denoting the type
              default     A list of tokens denoting the default value
              unknown     A list of tokens not assigned to type or default
      """
  
      __slots__ = ['name', 'mode', 'type', 'default', 'unknown']
  
      def __init__(self):
          self.name = None
          self.mode = 'IN'
          self.type = []
          self.default = []
          self.unknown = []
  
      def __getitem__(self, attr):
          return getattr(self, attr)
  
  
  def parse_typed_field_list(tokens):
      """Parses a argument/column list, yielding TypedFieldMetadata objects
  
          Field/column lists are used in function signatures and table
          definitions. This function parses a flattened list of sqlparse tokens
          and yields one metadata argument per argument / column.
      """
  
      # postgres function argument list syntax:
      #   " ( [ [ argmode ] [ argname ] argtype
      #               [ { DEFAULT | = } default_expr ] [, ...] ] )"
  
      mode_names = set(('IN', 'OUT', 'INOUT', 'VARIADIC'))
      parse_state = 'type'
      parens = 0
      field = TypedFieldMetadata()
  
      for tok in tokens:
          if tok.ttype in Whitespace or tok.ttype in Comment:
              continue
          elif tok.ttype in Punctuation:
              if parens == 0 and tok.value == ',':
                  # End of the current field specification
                  if field.type:
                      yield field
                  # Initialize metadata holder for the next field
                  field, parse_state = TypedFieldMetadata(), 'type'
              elif parens == 0 and tok.value == '=':
                  parse_state = 'default'
              else:
                  field[parse_state].append(tok)
                  if tok.value == '(':
                      parens += 1
                  elif tok.value == ')':
                      parens -= 1
          elif parens == 0:
              if tok.ttype in Keyword:
                  if not field.name and tok.value.upper() in mode_names:
                      # No other keywords allowed before arg name
                      field.mode = tok.value.upper()
                  elif tok.value.upper() == 'DEFAULT':
                      parse_state = 'default'
                  else:
                      parse_state = 'unknown'
              elif tok.ttype == Name and not field.name:
                  # note that `ttype in Name` would also match Name.Builtin
                  field.name = tok.value
              else:
                  field[parse_state].append(tok)
          else:
              field[parse_state].append(tok)
  
      # Final argument won't be followed by a comma, so make sure it gets
      # yielded
      if field.type:
          yield field
  
  
  def field_names(sql, mode_filter=('IN', 'OUT', 'INOUT', 'VARIADIC')):
      """Yields field names from a table declaration"""
  
      if not sql:
          return
  
      # sql is something like "x int, y text, ..."
      tokens = sqlparse.parse(sql)[0].flatten()
      for f in parse_typed_field_list(tokens):
          if f.name and (not mode_filter or f.mode in mode_filter):
              yield f.name
  