code: def execute_sql(self, result_type=constants.MULTI, chunked_fetch=False,
                      chunk_size=constants.GET_ITERATOR_CHUNK_SIZE):
          
          try:
              sql, params = self.as_sql()
              if not sql:
                  raise EmptyResultSet
          except EmptyResultSet:
              if result_type == constants.MULTI:
                  return iter([])
              else:
                  return
  
          cursor = self.connection.cursor()
          cursor.prepare_query(self.query)
          cursor.execute(sql, params)
  
          if not result_type or result_type == 'cursor':
              return cursor
  
          if result_type == constants.SINGLE:
              return cursor.fetchone()
  
          # The MULTI case.
          result = iter(lambda: cursor.fetchmany(chunk_size),
                        self.connection.features.empty_fetchmany_value)
          if not chunked_fetch and not self.connection.features.can_use_chunked_reads:
              # If we are using non-chunked reads, we return the same data
              # structure as normally, but ensure it is all read into memory
              # before going any further. Use chunked_fetch if requested.
              return list(result)
          return result
docs: Run the query against the database and returns the result(s). The
          return value is a single data item if result_type is SINGLE, or an
          iterator over the results if the result_type is MULTI.
  
          result_type is either MULTI (use fetchmany() to retrieve all rows),
          SINGLE (only retrieve a single row), or None. In this last case, the
          cursor is returned if any query is executed, since it's used by
          subclasses such as InsertQuery). It's possible, however, that no query
          is needed, as the filters describe an empty set. In that case, None is
          returned, to avoid any unnecessary database interaction.