blob: 593a242b8b03bdb9e4ae635a9afe3d9f3296a7c2 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import uuid
import weakref
from phoenixdb import errors
from phoenixdb.avatica.client import OPEN_CONNECTION_PROPERTIES
from phoenixdb.cursor import Cursor
from phoenixdb.errors import ProgrammingError
__all__ = ['Connection']
logger = logging.getLogger(__name__)
class Connection(object):
"""Database connection.
You should not construct this object manually, use :func:`~phoenixdb.connect` instead.
"""
cursor_factory = None
"""
The default cursor factory used by :meth:`cursor` if the parameter is not specified.
"""
def __init__(self, client, cursor_factory=None, **kwargs):
self._client = client
self._closed = False
if cursor_factory is not None:
self.cursor_factory = cursor_factory
else:
self.cursor_factory = Cursor
self._cursors = []
# Extract properties to pass to OpenConnectionRequest
self._connection_args = {}
# The rest of the kwargs
self._filtered_args = {}
for k in kwargs:
if k in OPEN_CONNECTION_PROPERTIES:
self._connection_args[k] = kwargs[k]
else:
self._filtered_args[k] = kwargs[k]
self.open()
self.set_session(**self._filtered_args)
def __del__(self):
if not self._closed:
self.close()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
if not self._closed:
self.close()
def open(self):
"""Opens the connection."""
self._id = str(uuid.uuid4())
self._client.open_connection(self._id, info=self._connection_args)
def close(self):
"""Closes the connection.
No further operations are allowed, either on the connection or any
of its cursors, once the connection is closed.
If the connection is used in a ``with`` statement, this method will
be automatically called at the end of the ``with`` block.
"""
if self._closed:
raise ProgrammingError('the connection is already closed')
for cursor_ref in self._cursors:
cursor = cursor_ref()
if cursor is not None and not cursor._closed:
cursor.close()
self._client.close_connection(self._id)
self._client.close()
self._closed = True
@property
def closed(self):
"""Read-only attribute specifying if the connection is closed or not."""
return self._closed
def commit(self):
"""Commits pending database changes.
Currently, this does nothing, because the RPC does not support
transactions. Only defined for DB API 2.0 compatibility.
You need to use :attr:`autocommit` mode.
"""
# TODO can support be added for this?
if self._closed:
raise ProgrammingError('the connection is already closed')
def cursor(self, cursor_factory=None):
"""Creates a new cursor.
:param cursor_factory:
This argument can be used to create non-standard cursors.
The class returned must be a subclass of
:class:`~phoenixdb.cursor.Cursor` (for example :class:`~phoenixdb.cursor.DictCursor`).
A default factory for the connection can also be specified using the
:attr:`cursor_factory` attribute.
:returns:
A :class:`~phoenixdb.cursor.Cursor` object.
"""
if self._closed:
raise ProgrammingError('the connection is already closed')
cursor = (cursor_factory or self.cursor_factory)(self)
self._cursors.append(weakref.ref(cursor, self._cursors.remove))
return cursor
def set_session(self, autocommit=None, readonly=None):
"""Sets one or more parameters in the current connection.
:param autocommit:
Switch the connection to autocommit mode. With the current
version, you need to always enable this, because
:meth:`commit` is not implemented.
:param readonly:
Switch the connection to read-only mode.
"""
props = {}
if autocommit is not None:
props['autoCommit'] = bool(autocommit)
if readonly is not None:
props['readOnly'] = bool(readonly)
props = self._client.connection_sync(self._id, props)
self._autocommit = props.auto_commit
self._readonly = props.read_only
self._transactionisolation = props.transaction_isolation
@property
def autocommit(self):
"""Read/write attribute for switching the connection's autocommit mode."""
return self._autocommit
@autocommit.setter
def autocommit(self, value):
if self._closed:
raise ProgrammingError('the connection is already closed')
props = self._client.connection_sync(self._id, {'autoCommit': bool(value)})
self._autocommit = props.auto_commit
@property
def readonly(self):
"""Read/write attribute for switching the connection's readonly mode."""
return self._readonly
@readonly.setter
def readonly(self, value):
if self._closed:
raise ProgrammingError('the connection is already closed')
props = self._client.connection_sync(self._id, {'readOnly': bool(value)})
self._readonly = props.read_only
@property
def transactionisolation(self):
return self._transactionisolation
@transactionisolation.setter
def transactionisolation(self, value):
if self._closed:
raise ProgrammingError('the connection is already closed')
props = self._client.connection_sync(self._id, {'transactionIsolation': bool(value)})
self._transactionisolation = props.transaction_isolation
for name in errors.__all__:
setattr(Connection, name, getattr(errors, name))