import json
import logging
import psycopg2
import select
import sys

from redash.query_runner import *
from redash.utils import JSONEncoder

logger = logging.getLogger(__name__)

types_map = {
    20: TYPE_INTEGER,
    21: TYPE_INTEGER,
    23: TYPE_INTEGER,
    700: TYPE_FLOAT,
    1700: TYPE_FLOAT,
    701: TYPE_FLOAT,
    16: TYPE_BOOLEAN,
    1082: TYPE_DATE,
    1114: TYPE_DATETIME,
    1184: TYPE_DATETIME,
    1014: TYPE_STRING,
    1015: TYPE_STRING,
    1008: TYPE_STRING,
    1009: TYPE_STRING,
    2951: TYPE_STRING
}


def _wait(conn):
    while 1:
        try:
            state = conn.poll()
            if state == psycopg2.extensions.POLL_OK:
                break
            elif state == psycopg2.extensions.POLL_WRITE:
                select.select([], [conn.fileno()], [])
            elif state == psycopg2.extensions.POLL_READ:
                select.select([conn.fileno()], [], [])
            else:
                raise psycopg2.OperationalError("poll() returned %s" % state)
        except select.error:
            raise psycopg2.OperationalError("select.error received")


class PostgreSQL(BaseQueryRunner):
    @classmethod
    def configuration_schema(cls):
        return {
            "type": "object",
            "properties": {
                "user": {
                    "type": "string"
                },
                "password": {
                    "type": "string"
                },
                "host": {
                    "type": "string"
                },
                "port": {
                    "type": "number"
                },
                "dbname": {
                    "type": "string",
                    "title": "Database Name"
                }
            },
            "required": ["dbname"]
        }

    @classmethod
    def type(cls):
        return "pg"

    def __init__(self, configuration_json):
        super(PostgreSQL, self).__init__(configuration_json)

        values = []
        for k, v in self.configuration.iteritems():
            values.append("{}={}".format(k, v))

        self.connection_string = " ".join(values)

    def get_schema(self):
        query = """
        SELECT table_schema, table_name, column_name
        FROM information_schema.columns
        WHERE table_schema NOT IN ('pg_catalog', 'information_schema');
        """

        results, error = self.run_query(query)

        if error is not None:
           raise Exception("Failed getting schema.")

        results = json.loads(results)

        schema = {}
        for row in results['rows']:
            if row['table_schema'] != 'public':
                table_name = '{}.{}'.format(row['table_schema'], row['table_name'])
            else:
                table_name = row['table_name']

            if table_name not in schema:
                schema[table_name] = {'name': table_name, 'columns': []}

            schema[table_name]['columns'].append(row['column_name'])

        return schema.values()

    def run_query(self, query):
        connection = psycopg2.connect(self.connection_string, async=True)
        _wait(connection)

        cursor = connection.cursor()

        try:
            cursor.execute(query)
            _wait(connection)

            # While set would be more efficient here, it sorts the data which is not what we want, but due to the small
            # size of the data we can assume it's ok.
            column_names = []
            columns = []
            duplicates_counter = 1

            for column in cursor.description:
                # TODO: this deduplication needs to be generalized and reused in all query runners.
                column_name = column.name
                if column_name in column_names:
                    column_name += str(duplicates_counter)
                    duplicates_counter += 1

                column_names.append(column_name)

                columns.append({
                    'name': column_name,
                    'friendly_name': column_name,
                    'type': types_map.get(column.type_code, None)
                })

            rows = [dict(zip(column_names, row)) for row in cursor]

            data = {'columns': columns, 'rows': rows}
            json_data = json.dumps(data, cls=JSONEncoder)
            error = None
            cursor.close()
        except (select.error, OSError) as e:
            logging.exception(e)
            error = "Query interrupted. Please retry."
            json_data = None
        except psycopg2.DatabaseError as e:
            logging.exception(e)
            json_data = None
            error = e.message
        except KeyboardInterrupt:
            connection.cancel()
            error = "Query cancelled by user."
            json_data = None
        except Exception as e:
            raise sys.exc_info()[1], None, sys.exc_info()[2]
        finally:
            connection.close()

        return json_data, error

register(PostgreSQL)
@@ -3493,24 +3493,75 @@
 ounter = 1%0A%0A
+            if cursor.description is not None:%0A    
             
@@ -3594,16 +3594,20 @@
 iption:%0A
+    
         
@@ -3706,32 +3706,36 @@
                 
+ 
+   
 column_name = co
@@ -3740,24 +3740,28 @@
 column.name%0A
+    
             
@@ -3808,32 +3808,36 @@
                 
+    
 column_name += s
@@ -3871,32 +3871,36 @@
                 
+   
+ 
 duplicates_count
@@ -3904,24 +3904,28 @@
 unter += 1%0A%0A
+    
             
@@ -3974,24 +3974,28 @@
             
+  
+  
 columns.appe
@@ -3999,16 +3999,20 @@
 ppend(%7B%0A
+    
         
@@ -4064,16 +4064,20 @@
         
+ 
+   
 'friendl
@@ -4090,32 +4090,36 @@
 ': column_name,%0A
+    
                 
@@ -4184,16 +4184,20 @@
         
+    
 %7D)%0A%0A    
@@ -4204,16 +4204,20 @@
         
+   
+ 
 rows = %5B
@@ -4277,16 +4277,20 @@
         
+    
 data = %7B
@@ -4323,16 +4323,49 @@
 : rows%7D%0A
+                error = None%0A    
         
@@ -4431,46 +4431,112 @@
    e
-rror = None%0A            cursor.close()
+lse:%0A                error = 'Query completed but it returned no data.'%0A                json_data = None
 %0A   
@@ -4787,53 +4787,53 @@
     
-json_data = None%0A            error = e.messag
+error = e.message%0A            json_data = Non
 e%0A  

Add a more use-friendly message when redshift returns no rows