##############################################################################
#The MIT License (MIT)
#
#Copyright (c) 2014-2017 Hajime Nakagami
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#SOFTWARE.
##############################################################################
# PostgreSQL driver for micropython https://github.com/micropython/micropython
# It's a minipg (https://github.com/nakagami/minipg) subset.
try:
    import usocket
except ImportError:
    import socket as usocket
try:
    import ubinascii
except ImportError:
    import binascii as ubinascii

VERSION = (0, 2, 0)
__version__ = '%s.%s.%s' % VERSION
apilevel = '2.0'
threadsafety = 1
paramstyle = 'format'

#-----------------------------------------------------------------------------
# http://www.postgresql.org/docs/9.3/static/protocol.html
# http://www.postgresql.org/docs/9.3/static/protocol-message-formats.html

# postgresql-9.3.5/src/include/catalog/pg_type.h
PG_TYPE_BOOL = 16
PG_TYPE_BYTEA = 17
PG_TYPE_CHAR = 18
PG_TYPE_NAME = 19
PG_TYPE_INT8 = 20
PG_TYPE_INT2 = 21
PG_TYPE_INT2VECTOR = 22
PG_TYPE_INT4 = 23
PG_TYPE_REGPROC = 24
PG_TYPE_TEXT = 25
PG_TYPE_OID = 26
PG_TYPE_TID = 27
PG_TYPE_XID = 28
PG_TYPE_CID = 29
PG_TYPE_VECTOROID = 30
PG_TYPE_JSON = 114
PG_TYPE_XML = 142
PG_TYPE_PGNODETREE = 194
PG_TYPE_POINT = 600
PG_TYPE_LSEG = 601
PG_TYPE_PATH = 602
PG_TYPE_BOX = 603
PG_TYPE_POLYGON = 604
PG_TYPE_LINE = 628
PG_TYPE_FLOAT4 = 700
PG_TYPE_FLOAT8 = 701
PG_TYPE_ABSTIME = 702
PG_TYPE_RELTIME = 703
PG_TYPE_TINTERVAL = 704
PG_TYPE_UNKNOWN = 705
PG_TYPE_CIRCLE = 718
PG_TYPE_CASH = 790
PG_TYPE_MACADDR = 829
PG_TYPE_INET = 869
PG_TYPE_CIDR = 650
PG_TYPE_NAMEARRAY = 1003
PG_TYPE_INT2ARRAY = 1005
PG_TYPE_INT4ARRAY = 1007
PG_TYPE_TEXTARRAY = 1009
PG_TYPE_ARRAYOID = 1028
PG_TYPE_FLOAT4ARRAY = 1021
PG_TYPE_ACLITEM = 1033
PG_TYPE_CSTRINGARRAY = 1263
PG_TYPE_BPCHAR = 1042
PG_TYPE_VARCHAR = 1043
PG_TYPE_DATE = 1082
PG_TYPE_TIME = 1083
PG_TYPE_TIMESTAMP = 1114
PG_TYPE_TIMESTAMPTZ = 1184
PG_TYPE_INTERVAL = 1186
PG_TYPE_TIMETZ = 1266
PG_TYPE_BIT = 1560
PG_TYPE_VARBIT = 1562
PG_TYPE_NUMERIC = 1700
PG_TYPE_REFCURSOR = 1790
PG_TYPE_REGPROCEDURE = 2202
PG_TYPE_REGOPER = 2203
PG_TYPE_REGOPERATOR = 2204
PG_TYPE_REGCLASS = 2205
PG_TYPE_REGTYPE = 2206
PG_TYPE_REGTYPEARRAY = 2211
PG_TYPE_UUID = 2950
PG_TYPE_TSVECTOR = 3614
PG_TYPE_GTSVECTOR = 3642
PG_TYPE_TSQUERY = 3615
PG_TYPE_REGCONFIG = 3734
PG_TYPE_REGDICTIONARY = 3769
PG_TYPE_INT4RANGE = 3904
PG_TYPE_RECORD = 2249
PG_TYPE_RECORDARRAY = 2287
PG_TYPE_CSTRING = 2275
PG_TYPE_ANY = 2276
PG_TYPE_ANYARRAY = 2277
PG_TYPE_VOID = 2278
PG_TYPE_TRIGGER = 2279
PG_TYPE_EVTTRIGGER = 3838
PG_TYPE_LANGUAGE_HANDLER = 2280
PG_TYPE_INTERNAL = 2281
PG_TYPE_OPAQUE = 2282
PG_TYPE_ANYELEMENT = 2283
PG_TYPE_ANYNONARRAY = 2776
PG_TYPE_ANYENUM = 3500
PG_TYPE_FDW_HANDLER = 3115
PG_TYPE_ANYRANGE = 3831


def _decode_column(data, oid, encoding):
    def _parse_point(data):
        x, y = data[1:-1].split(',')
        return (float(x), float(y))

    if data is None:
        return data
    data = data.decode(encoding)
    if oid in (PG_TYPE_BOOL,):
        return data == 't'
    elif oid in (PG_TYPE_INT2, PG_TYPE_INT4, PG_TYPE_INT8, PG_TYPE_OID,):
        return int(data)
    elif oid in (PG_TYPE_FLOAT4, PG_TYPE_FLOAT8):
        return float(data)
    elif oid in (PG_TYPE_BYTEA, ):
        assert data[:2] == u'\\x'
        hex_str = data[2:]
        ia = [int(hex_str[i:i+2], 16) for i in range(0, len(hex_str), 2)]
        return bytes(ia)
    elif oid in (PG_TYPE_CHAR, PG_TYPE_TEXT, PG_TYPE_BPCHAR, PG_TYPE_VARCHAR, PG_TYPE_NAME, PG_TYPE_JSON):
        return data
    elif oid in (PG_TYPE_UNKNOWN, PG_TYPE_PGNODETREE, PG_TYPE_TSVECTOR, PG_TYPE_INET):
        return data
    elif oid in (PG_TYPE_INT2ARRAY, PG_TYPE_INT4ARRAY):
        return [int(i) for i in data[1:-1].split(',')]
    elif oid in (PG_TYPE_NAMEARRAY, PG_TYPE_TEXTARRAY):
        return [s for s in data[1:-1].split(',')]
    elif oid in (PG_TYPE_FLOAT4ARRAY, ):
        return [float(f) for f in data[1:-1].split(',')]
    elif oid in (PG_TYPE_INT2VECTOR, ):
        return [int(i) for i in data.split(' ')]
    elif oid in (PG_TYPE_POINT, ):
        return _parse_point(data)
    elif oid in (PG_TYPE_CIRCLE, ):
        p = data[1:data.find(')')+1]
        r = data[len(p)+2:-1]
        return (_parse_point(p), float(r))
    elif oid in (PG_TYPE_LSEG, PG_TYPE_PATH, PG_TYPE_BOX, PG_TYPE_POLYGON, PG_TYPE_LINE):
        return eval(data)
    return data

# ----------------------------------------------------------------------------

def _bytes_to_bint(b):      # Read as big endian
    r = 0
    for n in b:
        r = r * 256 + n
    return r


def _bint_to_bytes(val):    # Convert int value to big endian 4 bytes.
    return bytes([(val >> 24) & 0xff, (val >> 16) & 0xff, (val >> 8) & 0xff, val & 0xff])


def _bytes_to_int(b):       # Read as little endian
    r = 0
    for n in reversed(b):
        r = r * 256 + n
    return r


def _int_to_bytes(val):    # Convert int value to little endian 4 bytes.
    return bytes([val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff])


def _md5_hexdigest(message):
    # support only less than 56 bytes
    assert len(message) < 56

    def _bit_not(n): return ((1 << 32) - 1 - n) & 0xffffffff
    def F(x,y,z):   return (z ^ (x & (y ^ z))) & 0xffffffff
    def G(x,y,z):   return (y ^ (z & (y ^ x))) & 0xffffffff
    def H(x,y,z):   return (x ^ y ^ z) & 0xffffffff
    def I(x,y,z):   return (y ^ (x | _bit_not(z))) & 0xffffffff
    def ROTATE_LEFT(x, n): return ((x << n) | (x >> (32-n))) & 0xffffffff
    def FF(a, b, c, d, x, s, ac):
        a = (a + F(b, c, d) + x + ac) & 0xffffffff
        a = (ROTATE_LEFT(a, s) + b) & 0xffffffff
        return a
    def GG(a, b, c, d, x, s, ac):
        a = (a + G(b, c, d) + x + ac) & 0xffffffff
        a =  (ROTATE_LEFT(a, s) + b) & 0xffffffff
        return a
    def HH(a, b, c, d, x, s, ac):
        a = (a + H(b, c, d) + x + ac) & 0xffffffff
        a = (ROTATE_LEFT(a, s) + b) & 0xffffffff
        return a
    def II(a, b, c, d, x, s, ac):
        a = (a + I(b, c, d) + x + ac) & 0xffffffff
        a = (ROTATE_LEFT(a, s) + b) & 0xffffffff
        return a

    message_len = len(message)
    data = message + b'\x80' + b'\00' * (55 - message_len) + _int_to_bytes(message_len*8) + b'\x00\x00\x00\x00'
    W = [_bytes_to_int(b) for b in zip(*[iter(data)]*4)]
    assert len(W) == 16

    a0 = a = 0x67452301
    b0 = b = 0xefcdab89
    c0 = c = 0x98badcfe
    d0 = d = 0x10325476

    a = FF(a,b,c,d,W[0],7,0xd76aa478)
    d = FF(d,a,b,c,W[1],12,0xe8c7b756)
    c = FF(c,d,a,b,W[2],17,0x242070db)
    b = FF(b,c,d,a,W[3],22,0xc1bdceee)
    a = FF(a,b,c,d,W[4],7,0xf57c0faf)
    d = FF(d,a,b,c,W[5],12,0x4787c62a)
    c = FF(c,d,a,b,W[6],17,0xa8304613)
    b = FF(b,c,d,a,W[7],22,0xfd469501)
    a = FF(a,b,c,d,W[8],7,0x698098d8)
    d = FF(d,a,b,c,W[9],12,0x8b44f7af)
    c = FF(c,d,a,b,W[10],17,0xffff5bb1)
    b = FF(b,c,d,a,W[11],22,0x895cd7be)
    a = FF(a,b,c,d,W[12],7,0x6b901122)
    d = FF(d,a,b,c,W[13],12,0xfd987193)
    c = FF(c,d,a,b,W[14],17,0xa679438e)
    b = FF(b,c,d,a,W[15],22,0x49b40821)

    a = GG(a,b,c,d,W[1],5,0xf61e2562)
    d = GG(d,a,b,c,W[6],9,0xc040b340)
    c = GG(c,d,a,b,W[11],14,0x265e5a51)
    b = GG(b,c,d,a,W[0],20,0xe9b6c7aa)
    a = GG(a,b,c,d,W[5],5,0xd62f105d)
    d = GG(d,a,b,c,W[10],9,0x02441453)
    c = GG(c,d,a,b,W[15],14,0xd8a1e681)
    b = GG(b,c,d,a,W[4],20,0xe7d3fbc8)
    a = GG(a,b,c,d,W[9],5,0x21e1cde6)
    d = GG(d,a,b,c,W[14],9,0xc33707d6)
    c = GG(c,d,a,b,W[3],14,0xf4d50d87)
    b = GG(b,c,d,a,W[8],20,0x455a14ed)
    a = GG(a,b,c,d,W[13],5,0xa9e3e905)
    d = GG(d,a,b,c,W[2],9,0xfcefa3f8)
    c = GG(c,d,a,b,W[7],14,0x676f02d9)
    b = GG(b,c,d,a,W[12],20,0x8d2a4c8a)

    a = HH(a,b,c,d,W[5],4,0xfffa3942)
    d = HH(d,a,b,c,W[8],11,0x8771f681)
    c = HH(c,d,a,b,W[11],16,0x6d9d6122)
    b = HH(b,c,d,a,W[14],23,0xfde5380c)
    a = HH(a,b,c,d,W[1],4,0xa4beea44)
    d = HH(d,a,b,c,W[4],11,0x4bdecfa9)
    c = HH(c,d,a,b,W[7],16,0xf6bb4b60)
    b = HH(b,c,d,a,W[10],23,0xbebfbc70)
    a = HH(a,b,c,d,W[13],4,0x289b7ec6)
    d = HH(d,a,b,c,W[0],11,0xeaa127fa)
    c = HH(c,d,a,b,W[3],16,0xd4ef3085)
    b = HH(b,c,d,a,W[6],23,0x04881d05)
    a = HH(a,b,c,d,W[9],4,0xd9d4d039)
    d = HH(d,a,b,c,W[12],11,0xe6db99e5)
    c = HH(c,d,a,b,W[15],16,0x1fa27cf8)
    b = HH(b,c,d,a,W[2],23,0xc4ac5665)

    a = II(a,b,c,d,W[0],6,0xf4292244)
    d = II(d,a,b,c,W[7],10,0x432aff97)
    c = II(c,d,a,b,W[14],15,0xab9423a7)
    b = II(b,c,d,a,W[5],21,0xfc93a039)
    a = II(a,b,c,d,W[12],6,0x655b59c3)
    d = II(d,a,b,c,W[3],10,0x8f0ccc92)
    c = II(c,d,a,b,W[10],15,0xffeff47d)
    b = II(b,c,d,a,W[1],21,0x85845dd1)
    a = II(a,b,c,d,W[8],6,0x6fa87e4f)
    d = II(d,a,b,c,W[15],10,0xfe2ce6e0)
    c = II(c,d,a,b,W[6],15,0xa3014314)
    b = II(b,c,d,a,W[13],21,0x4e0811a1)
    a = II(a,b,c,d,W[4],6,0xf7537e82)
    d = II(d,a,b,c,W[11],10,0xbd3af235)
    c = II(c,d,a,b,W[2],15,0x2ad7d2bb)
    b = II(b,c,d,a,W[9],21,0xeb86d391)

    a = (a0 + a) & 0xffffffff
    b = (b0 + b) & 0xffffffff
    c = (c0 + c) & 0xffffffff
    d = (d0 + d) & 0xffffffff

    # import hashlib; return hashlib.md5(message).hexdigest().encode("ascii")
    return ubinascii.hexlify(_int_to_bytes(a) + _int_to_bytes(b) + _int_to_bytes(c) + _int_to_bytes(d))


class Error(Exception):
    def __init__(self, *args):
        if len(args) > 0:
            self.message = args[0]
        else:
            self.message = b'Database Error'
        super(Error, self).__init__(*args)

    def __str__(self):
        return self.message

    def __repr__(self):
        return self.message


class Warning(Exception):
    pass


class InterfaceError(Error):
    pass


class DatabaseError(Error):
    pass


class OperationalError(DatabaseError):
    pass


class ProgrammingError(DatabaseError):
    pass


class IntegrityError(DatabaseError):
    pass


class NotSupportedError(DatabaseError):
    def __init__(self):
        DatabaseError.__init__(self, 'NotSupportedError')


class Cursor(object):
    def __init__(self, connection):
        self.connection = connection
        self.description = []
        self._rows = []
        self._rowcount = 0
        self.arraysize = 1
        self.query = None

    def __enter__(self):
        return self

    def __exit__(self, exc, value, traceback):
        self.close()

    def callproc(self, procname, args=()):
        raise NotSupportedError()

    def nextset(self, procname, args=()):
        raise NotSupportedError()

    def setinputsizes(sizes):
        pass

    def setoutputsize(size, column=None):
        pass

    def execute(self, query, args=()):
        if not self.connection or not self.connection.is_connect():
            raise ProgrammingError(u"08003:Lost connection")
        self.description = []
        self._rows.clear()
        self.args = args
        if args:
            escaped_args = tuple(
                self.connection.escape_parameter(arg).replace(u'%', u'%%') for arg in args
            )
            query = query.replace(u'%', u'%%').replace(u'%%s', u'%s')
            query = query % escaped_args
            query = query.replace(u'%%', u'%')
        self.query = query
        self.connection.execute(query, self)

    def executemany(self, query, seq_of_params):
        rowcount = 0
        for params in seq_of_params:
            self.execute(query, params)
            rowcount += self._rowcount
        self._rowcount = rowcount

    def fetchone(self):
        if not self.connection or not self.connection.is_connect():
            raise OperationalError(u"08003:Lost connection")
        if len(self._rows):
            r = self._rows[0]
            self._rows = self._rows[1:]
            return r
        return None

    def fetchmany(self, size=1):
        rs = []
        for i in range(size):
            r = self.fetchone()
            if not r:
                break
            rs.append(r)
        return rs

    def fetchall(self):
        r = list(self._rows)
        self._rows.clear()
        return r

    def close(self):
        self.connection = None

    @property
    def rowcount(self):
        return self._rowcount

    @property
    def closed(self):
        return self.connection is None or not self.connection.is_connect()

    def __iter__(self):
        return self

    def __next__(self):
        r = self.fetchone()
        if not r:
            raise StopIteration()
        return r

    def next(self):
        return self.__next__()


class Connection(object):
    def __init__(self, user, password, database, host, port, timeout):
        self.user = user
        self.password = password
        self.database = database
        self.host = host
        self.port = port
        self.timeout = timeout
        self.encoding = 'UTF8'
        self.autocommit = False
        self._ready_for_query = b'I'
        self._open()
        self.encoders = {}

    def __enter__(self):
        return self

    def __exit__(self, exc, value, traceback):
        self.close()

    def _send_message(self, message, data):
        self._write(b''.join([message, _bint_to_bytes(len(data) + 4), data, b'H\x00\x00\x00\x04']))

    def _process_messages(self, obj):
        errobj = None
        while True:
            try:
                code = ord(self._read(1))
            except OperationalError:
                # something error occured
                break
            ln = _bytes_to_bint(self._read(4)) - 4
            data = self._read(ln)
            if code == 90:  # ReadyForQuery('Z')
                self._ready_for_query = data
                break
            elif code == 82:    # Authenticaton('R')
                auth_method = _bytes_to_bint(data[:4])
                if auth_method == 0:      # trust
                    pass
                elif auth_method == 5:    # md5
                    salt = data[4:]
                    h1 = _md5_hexdigest(self.password.encode('ascii') + self.user.encode("ascii"))
                    h2 = _md5_hexdigest(h1+salt)
                    self._send_message(b'p', b''.join([b'md5', h2, b'\x00']))
                else:
                    errobj = InterfaceError("Authentication method %d not supported." % (auth_method,))
            elif code == 83:    # ParameterStatus('S')
                k, v, _ = data.split(b'\x00')
                if k == b'server_encoding':
                    self.encoding = v.decode('ascii')
            elif code == 75:    # BackendKeyData('K')
                pass
            elif code == 67:    # CommandComplete('C')
                if not obj:
                    continue
                command = data[:-1].decode('ascii')
                if command == 'SHOW':
                    obj._rowcount = 1
                else:
                    for k in ('SELECT', 'UPDATE', 'DELETE', 'INSERT'):
                        if command[:len(k)] == k:
                            obj._rowcount = int(command.split(' ')[-1])
                            break
            elif code == 84:    # RowDescription('T')
                if not obj:
                    continue
                count = _bytes_to_bint(data[0:2])
                obj.description = [None] * count
                n = 2
                idx = 0
                for i in range(count):
                    name = data[n:n+data[n:].find(b'\x00')]
                    n += len(name) + 1
                    try:
                        name = name.decode(self.encoding)
                    except UnicodeDecodeError:
                        pass
                    type_code = _bytes_to_bint(data[n+6:n+10])
                    if type_code == PG_TYPE_VARCHAR:
                        size = _bytes_to_bint(data[n+12:n+16]) - 4
                        precision = -1
                        scale = -1
                    elif type_code == PG_TYPE_NUMERIC:
                        size = _bytes_to_bint(data[n+10:n+12])
                        precision = _bytes_to_bint(data[n+12:n+14])
                        scale = precision - _bytes_to_bint(data[n+14:n+16])
                    else:
                        size = _bytes_to_bint(data[n+10:n+12])
                        precision = -1
                        scale = -1
#                        table_oid = _bytes_to_bint(data[n:n+4])
#                        table_pos = _bytes_to_bint(data[n+4:n+6])
#                        size = _bytes_to_bint(data[n+10:n+12])
#                        modifier = _bytes_to_bint(data[n+12:n+16])
#                        format = _bytes_to_bint(data[n+16:n+18]),
                    field = (name, type_code, None, size, precision, scale, None)
                    n += 18
                    obj.description[idx] = field
                    idx += 1
            elif code == 68:    # DataRow('D')
                if not obj:
                    continue
                n = 2
                row = []
                while n < len(data):
                    if data[n:n+4] == b'\xff\xff\xff\xff':
                        row.append(None)
                        n += 4
                    else:
                        ln = _bytes_to_bint(data[n:n+4])
                        n += 4
                        row.append(data[n:n+ln])
                        n += ln
                for i in range(len(row)):
                    row[i] = _decode_column(row[i], obj.description[i][1], self.encoding)
                obj._rows.append(tuple(row))
            elif code == 78:    # NoticeResponse('N')
                pass
            elif code == 69 and not errobj:     # ErrorResponse('E')
                err = data.split(b'\x00')
                # http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html
                errcode = err[1][1:]
                message = errcode + b':' + err[2][1:]
                message = message.decode(self.encoding)
                if errcode[:2] == b'23':
                    errobj = IntegrityError(message)
                else:
                    errobj = DatabaseError(message)
            elif code == 72:    # CopyOutputResponse('H')
                pass
            elif code == 100:   # CopyData('d')
                obj.write(data)
            elif code == 99:    # CopyDataDone('c')
                pass
            elif code == 71:    # CopyInResponse('G')
                while True:
                    buf = obj.read(8192)
                    if not buf:
                        break
                    # send CopyData
                    self._write(b'd' + _bint_to_bytes(len(buf) + 4))
                    self._write(buf)
                # send CopyDone and Sync
                self._write(b'c\x00\x00\x00\x04S\x00\x00\x00\x04')
            else:
                pass
        return errobj

    def process_messages(self, obj):
        err = self._process_messages(obj)
        if err:
            raise err

    def _read(self, ln):
        if not self.sock:
            raise OperationalError(u"08003:Lost connection")
        r = b''
        while len(r) < ln:
            b = self.sock.recv(ln-len(r))
            if not b:
                raise OperationalError(u"08003:Can't recv packets")
            r += b
        return r

    def _write(self, b):
        if not self.sock:
            raise OperationalError(u"08003:Lost connection")
        n = 0
        while (n < len(b)):
            n += self.sock.send(b[n:])

    def _open(self):
        self.sock = usocket.socket()
        self.sock.connect(usocket.getaddrinfo(self.host, self.port)[0][-1])

        if self.timeout is not None:
            self.sock.settimeout(float(self.timeout))
        # protocol version 3.0
        v = b'\x00\x03\x00\x00'
        v += b'user\x00' + self.user.encode('ascii') + b'\x00'
        if self.database:
            v += b'database\x00' + self.database.encode('ascii') + b'\x00'
        v += b'\x00'

        self._write(_bint_to_bytes(len(v) + 4) + v)
        self.process_messages(None)

    def escape_parameter(self, v):
        t = type(v)
        func = self.encoders.get(t)
        if func:
            return func(self, v)
        if v is None:
            return 'NULL'
        elif t == str:  # string
            return u"'" + v.replace(u"'", u"''") + u"'"
        elif t == bytearray or t == bytes:        # binary
            return "'" + ''.join(['\\%03o' % (c, ) for c in v]) + "'::bytea"
        elif t == bool:
            return u"TRUE" if v else u"FALSE"
        elif t == list or t == tuple:
            return u'ARRAY[' + u','.join([self.escape_parameter(e) for e in v]) + u']'
        else:
            return "'" + str(v) + "'"

    @property
    def is_dirty(self):
        return self._ready_for_query in b'TE'

    def is_connect(self):
        return bool(self.sock)

    def cursor(self):
        return Cursor(self)

    def _execute(self, query, obj):
        self._send_message(b'Q', query.encode(self.encoding) + b'\x00')
        self.process_messages(obj)
        if self.autocommit:
            self.commit()

    def execute(self, query, obj=None):
        if self._ready_for_query != b'T':
            self.begin()
        self._execute(query, obj)

    def set_autocommit(self, autocommit):
        self.autocommit = autocommit

    def begin(self):
        if self._ready_for_query == b'E':
            self.rollback()
        self._send_message(b'Q', b"BEGIN\x00")
        self._process_messages(None)

    def commit(self):
        if self.sock:
            self._send_message(b'Q', b"COMMIT\x00")
            self.process_messages(None)

    def rollback(self):
        if self.sock:
            self._send_message(b'Q', b"ROLLBACK\x00")
        self.process_messages(None)

    def reopen(self):
        self.close()
        self._open()

    def close(self):
        if self.sock:
            # send Terminate
            self._write(b'X\x00\x00\x00\x04')
            self.sock.close()
            self.sock = None


def connect(host, user, password='', database=None, port=5432, timeout=None):
    return Connection(user, password, database, host, port, timeout)
