Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pyignite/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@
from pyignite.client import Client
from pyignite.aio_client import AioClient
from pyignite.binary import GenericObjectMeta
from .dbapi import connect

__version__ = '0.6.0-dev'

__all__ = [ 'Client', 'connect' ]
87 changes: 87 additions & 0 deletions pyignite/dbapi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#
# Copyright 2021 GridGain Systems, Inc. and Contributors.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please change file header to appropriate one

#
# Licensed under the GridGain Community Edition License (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
#
# 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.
#

from .dbclient import DBClient
from .errors import *
from .. import constants
from urllib.parse import urlparse, parse_qs

apiLevel = '2.0'
threadsafety = 2
paramstyle = 'qmark'

def connect(dsn=None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if ignite node is out? We should support multiple nodes

user=None, password=None,
host=constants.IGNITE_DEFAULT_HOST, port=constants.IGNITE_DEFAULT_PORT,
**kwargs):
"""
Create a new database connection.

The connection can be specified via DSN:

``conn = connect("ignite://localhost/test?param1=value1&...")``

or using database and credentials arguments:

``conn = connect(database="test", user="default", password="default",
host="localhost", **kwargs)``

The basic connection parameters are:

- *host*: host with running Ignite server.
- *port*: port Ignite server is bound to.
- *database*: database connect to.
- *user*: database user.
- *password*: user's password.

See defaults in :data:`~pyignite.connection.Connection`
constructor.

DSN or host is required.

Any other keyword parameter will be passed to the underlying Connection
class.

:return: a new connection.
"""

if dsn is not None:
parsed_dsn = _parse_dsn(dsn)
host = parsed_dsn['host']
port = parsed_dsn['port']

client = DBClient()
client.connect(host, port)

return client

def _parse_dsn(dsn):
url_components = urlparse(dsn)
host = url_components.hostname
port = url_components.port or 10800
if url_components.path is not None:
schema = url_components.path.replace('/', '')
else:
schema = 'PUBLIC'
schema = url_components.path
return { 'host':host, 'port':port, 'schema':schema }

__all__ = [
'connect', 'apiLevel', 'threadsafety', 'paramstyle',
'Warning', 'Error', 'DataError', 'DatabaseError', 'ProgrammingError',
'IntegrityError', 'InterfaceError', 'InternalError', 'NotSupportedError',
'OperationalError', 'IgniteDialect'
]
47 changes: 47 additions & 0 deletions pyignite/dbapi/dbclient.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#
# Copyright 2021 GridGain Systems, Inc. and Contributors.
#
# Licensed under the GridGain Community Edition License (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
#
# 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.
#

from ..client import Client
from .dbcursor import DBCursor

class DBClient (Client):


def close(self):
"""
"""
# TODO: close open cursors
super.close()

def commit(self):
"""
Ignite doesn't have SQL transactions
"""
pass

def rollback(self):
"""
Ignite doesn't have SQL transactions
"""
pass

def cursor(self):
"""
Cursors work slightly differently in Ignite versus DBAPI, so
we map from one to the other
"""
return DBCursor(self)

140 changes: 140 additions & 0 deletions pyignite/dbapi/dbcursor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#
# Copyright 2021 GridGain Systems, Inc. and Contributors.
#
# Licensed under the GridGain Community Edition License (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
#
# 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.
#

from ..cursors import SqlCursor

class DBCursor(object):

def __init__(self, connection):
self.connection = connection
self.cursor = None
self.rowcount = -1

@property
def description(self):
# columns = self._columns
# types = [ bool ]

return [
[name, None, None, None, None, None, True]
for name in self._columns
# for name, type_code in zip(columns, types)
]

def close(self):
"""
Close the cursor now. The cursor will be unusable from this point
forward; an :data:`~pyignite.dbapi.Error` (or subclass)
exception will be raised if any operation is attempted with the
cursor.
"""
# self.connection.disconnect()
# self._state = self._states.CURSOR_CLOSED

# try:
# # cursor can be already closed
# self.connection.cursors.remove(self)
# except ValueError:
# pass

def execute(self, operation, parameters=None):
"""
Prepare and execute a database operation (query or command).

:param operation: query or command to execute.
:param parameters: sequence or mapping that will be bound to
variables in the operation.
:return: None
"""
self.cursor = self.connection.sql(operation, query_args=parameters, include_field_names=True)
self._columns = next(self.cursor)

def executemany(self, operation, seq_of_parameters):
"""
Prepare a database operation (query or command) and then execute it
against all parameter sequences found in the sequence
`seq_of_parameters`.

:param operation: query or command to execute.
:param seq_of_parameters: sequences or mappings for execution.
:return: None
"""
pass

def fetchone(self):
"""
Fetch the next row of a query result set, returning a single sequence,
or None when no more data is available.

:return: the next row of a query result set or None.
"""
if self.cursor is not None:
return next(self.cursor)
else:
return None

def fetchmany(self, size=None):
"""
Fetch the next set of rows of a query result, returning a sequence of
sequences (e.g. a list of tuples). An empty sequence is returned when
no more rows are available.

:param size: amount of rows to return.
:return: list of fetched rows or empty list.
"""
self._check_query_started()

if size is None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose you should use low level API
pyignite.api.sql.sql_fields
pyignite.api.sql.sql_fields_cursor_get_page

You can open cursor using the first call and retrieve next pages using the next call. Please, see how current cursors are implemented.

size = self.arraysize

if self._stream_results:
if size == -1:
return list(self._rows)
else:
return list(islice(self._rows, size))

if size < 0:
rv = self._rows
self._rows = []
else:
rv = self._rows[:size]
self._rows = self._rows[size:]

return rv

def fetchall(self):
"""
Fetch all (remaining) rows of a query result, returning them as a
sequence of sequences (e.g. a list of tuples).

:return: list of fetched rows.
"""
if self.cursor != None:
rows = []
for row in self.cursor:
rows.append(row)
else:
return None # error?

return rows

def setinputsizes(self, sizes):
# Do nothing.
pass

def setoutputsize(self, size, column=None):
# Do nothing.
pass
45 changes: 45 additions & 0 deletions pyignite/dbapi/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#
# Copyright 2021 GridGain Systems, Inc. and Contributors.
#
# Licensed under the GridGain Community Edition License (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
#
# 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.
#

class Warning(Exception):
pass

class Error(Exception):
pass

class InterfaceError(Error):
pass

class DatabaseError(Error):
pass

class InternalError(DatabaseError):
pass

class OperationalError(DatabaseError):
pass

class ProgrammingError(DatabaseError):
pass

class IntegrityError(DatabaseError):
pass

class DataError(DatabaseError):
pass

class NotSupportedError(DatabaseError):
pass
Loading