1#
2# Copyright (c) 2012-2018 JEP AUTHORS.
3#
4# This file is licensed under the the zlib/libpng License.
5#
6# This software is provided 'as-is', without any express or implied
7# warranty. In no event will the authors be held liable for any
8# damages arising from the use of this software.
9#
10# Permission is granted to anyone to use this software for any
11# purpose, including commercial applications, and to alter it and
12# redistribute it freely, subject to the following restrictions:
13#
14#     1. The origin of this software must not be misrepresented; you
15#     must not claim that you wrote the original software. If you use
16#     this software in a product, an acknowledgment in the product
17#     documentation would be appreciated but is not required.
18#
19#     2. Altered source versions must be plainly marked as such, and
20#     must not be misrepresented as being the original software.
21#
22#     3. This notice may not be removed or altered from any source
23#     distribution.
24#
25
26from decimal import Decimal
27import logging
28import java.sql
29import datetime
30import time
31
32
33log = logging.getLogger('java.sql')
34
35apilevel = '2.0'
36paramstyle = 'qmark'
37threadsafety = 2
38
39
40class Warning(Exception):
41    """Exception raised for important warnings like data
42    truncations while inserting, etc. It must be a subclass of
43    the Python StandardError (defined in the module
44    exceptions)."""
45    pass
46
47
48class Error(Exception):
49    """Exception that is the base class of all other error
50    exceptions. You can use this to catch all errors with one
51    single 'except' statement. Warnings are not considered
52    errors and thus should not use this class as base. It must
53    be a subclass of the Python StandardError (defined in the
54    module exceptions).
55    """
56    pass
57
58
59class InterfaceError(Error):
60    """Exception raised for errors that are related to the
61    database interface rather than the database itself.  It
62    must be a subclass of Error."""
63    pass
64
65
66class DatabaseError(Error):
67    """Exception raised for errors that are related to the
68    database.  It must be a subclass of Error."""
69    pass
70
71
72class DataError(DatabaseError):
73    """Exception raised for errors that are due to problems with
74    the processed data like division by zero, numeric value
75    out of range, etc. It must be a subclass of DatabaseError."""
76    pass
77
78
79class OperationalError(DatabaseError):
80    """Exception raised for errors that are related to the
81    database's operation and not necessarily under the control
82    of the programmer, e.g. an unexpected disconnect occurs,
83    the data source name is not found, a transaction could not
84    be processed, a memory allocation error occurred during
85    processing, etc.  It must be a subclass of DatabaseError."""
86    pass
87
88
89class IntegrityError(DatabaseError):
90    """Exception raised when the relational integrity of the
91    database is affected, e.g. a foreign key check fails.  It
92    must be a subclass of DatabaseError."""
93    pass
94
95
96class InternalError(DatabaseError):
97    """Exception raised when the database encounters an internal
98    error, e.g. the cursor is not valid anymore, the
99    transaction is out of sync, etc.  It must be a subclass of
100    DatabaseError."""
101    pass
102
103
104class ProgrammingError(DatabaseError):
105    """Exception raised for programming errors, e.g. table not
106    found or already exists, syntax error in the SQL
107    statement, wrong number of parameters specified, etc.  It
108    must be a subclass of DatabaseError."""
109    pass
110
111
112class NotSupportedError(DatabaseError):
113    """"Exception raised in case a method or database API was used
114    which is not supported by the database, e.g. requesting a
115    .rollback() on a connection that does not support
116    transaction or has transactions turned off.  It must be a
117    subclass of DatabaseError."""
118    pass
119
120
121def Date(year, month, day):
122    return java.sql.Date(int(time.mktime(datetime.date(year, month, day).timetuple())) * 1000)
123
124
125def Time(hour, minute, second):
126    return java.sql.Time(hour, minute, second)
127
128
129def Timestamp(year, month, day, hour, minute, second):
130    return java.sql.Timestamp(
131        int(time.mktime(datetime.datetime(year, month, day, hour, minute, second).timetuple())) * 1000)
132
133
134def DateFromTicks(ticks):
135    return Date(*time.localtime(ticks)[:3])
136
137
138def TimeFromTicks(ticks):
139    return Time(*time.localtime(ticks)[3:6])
140
141
142def TimestampFromTicks(ticks):
143    return Timestamp(*time.localtime(ticks)[:6])
144
145
146def connect(url, user=None, password=None, timeout=0):
147    """
148    Connect to a JDBC data source using java.sql.DriverManager.
149    Returns a java.sql.Connection instance.
150
151    url - JDBC connection url. See your driver's documentation for this value
152    user - connection username
153    password - optional password
154    timeout - time in seconds to wait for a connection.
155
156    Raises ``Error`` for failures.
157    """
158    try:
159        java.sql.DriverManager.setLoginTimeout(timeout)
160        return JDBCConnection(java.sql.DriverManager.getConnection(url, user, password))
161    except Exception as e:
162        raise Error(e.args)
163
164
165class JDBCConnection(object):
166
167    def __init__(self, conn):
168        super(JDBCConnection, self).__init__()
169        self.conn = conn
170
171    def close(self):
172        try:
173            self.conn.close()
174        except Exception as e:
175            raise DatabaseError(e.args)
176
177    def commit(self):
178        try:
179            self.conn.commit()
180        except Exception as e:
181            raise DatabaseError(e.args)
182
183    def rollback(self):
184        try:
185            self.conn.rollback()
186        except Exception as e:
187            raise DatabaseError(e.args)
188
189    def cursor(self):
190        return JDBCCursor(self)
191
192
193class JDBCCursor(object):
194
195    def __init__(self, jconn):
196        super(JDBCCursor, self).__init__()
197        self.connection = jconn
198        self.statement = None     # jdbc Statement
199        self.rs = None            # jdbc ResultSet
200        self.meta_data = None     # jdbc ResultSetMetaData
201        self.columns = None       # column count
202
203        self.description = ()
204        self.rowcount = None
205        self.arraysize = 1
206
207    def _prepare(self, sql):
208        self.rs = None
209        self.meta_data = None
210        self.columns = None
211        self.rowcount = None
212
213        self.statement = self.connection.conn.prepareStatement(sql)
214
215    def _set_parameters(self, args):
216        for index, arg in enumerate(args):
217            index += 1
218
219            if isinstance(arg, int):
220                self.statement.setLong(index, arg)
221            elif isinstance(arg, float):
222                self.statement.setDouble(index, arg)
223            elif isinstance(arg, str):
224                self.statement.setString(index, arg)
225            else:
226                self.statement.setObject(index, arg)
227
228    def execute(self, operation, *args):
229        try:
230            if log.isEnabledFor(logging.DEBUG):
231                log.debug('%s -- %s', operation.strip(), str(args))
232
233            self._prepare(operation)
234            self._set_parameters(args)
235
236            is_update = not self.statement.execute()
237            if is_update:
238                self.rowcount = self.statement.getUpdateCount()
239            else:
240                self.rs = self.statement.getResultSet()
241                self.meta_data = self.rs.getMetaData()
242                self.columns = self.meta_data.getColumnCount()
243
244                desc = []
245                for col in range(1, self.columns + 1):
246                    desc.append((
247                        self.meta_data.getColumnName(col),
248                        self.meta_data.getColumnType(col),
249                        self.meta_data.getColumnDisplaySize(col),
250                        self.meta_data.getColumnDisplaySize(col),
251                        self.meta_data.getPrecision(col),
252                        self.meta_data.getScale(col),
253                        bool(self.meta_data.isNullable(col)),
254                    ))
255                self.description = tuple(desc)
256
257        except Exception as e:
258            raise DatabaseError(str(e.args[0]))
259
260    def executemany(self, operation, seq_of_parameters):
261        if log.isEnabledFor(logging.DEBUG):
262            log.debug('executemany: %s', operation.strip())
263
264        self._prepare(operation)
265        for args in seq_of_parameters:
266            self._set_parameters(args)
267            self.statement.addBatch()
268
269        self.statement.executeBatch()
270
271    def fetchone(self):
272        if not self.rs.next():
273            return None
274
275        def map_type(col):
276            try:
277                sql_type = self.description[col - 1][1]
278
279                # see http://docs.oracle.com/javase/6/docs/api/constant-values.html#java.sql.Types.VARCHAR
280                if self.rs.getString(col) is None:
281                    return None
282                if sql_type in (-5, -7, 4, 5, -6):
283                    return self.rs.getLong(col)
284                if sql_type in (8, 6, 7):
285                    return self.rs.getDouble(col)
286                if sql_type in (-1, 1, 0, 12, -16, -9, -15):
287                    return self.rs.getString(col)
288                if sql_type in (16,):
289                    return self.rs.getBoolean(col)
290                if sql_type in (2, 3,):
291                    return Decimal(self.rs.getString(col))
292                if sql_type in (91,):
293                    return self.rs.getDate(col)
294                if sql_type in (92,):
295                    return self.rs.getTime(col)
296                if sql_type in (93,):
297                    return self.rs.getTimestamp(col)
298
299                return self.rs.getObject(col)
300            except Exception as e:
301                log.exception("Failed to map ResultSet type")
302                raise DatabaseError(e.args)
303
304            #public static final int 	BLOB 	2004
305            #public static final int 	CLOB 	2005
306            #public static final int 	DATALINK 	70
307            #public static final int 	DISTINCT 	2001
308            #public static final int 	JAVA_OBJECT 	2000
309            #public static final int 	LONGVARBINARY 	-4
310            #public static final int 	NCLOB 	2011
311            #public static final int 	OTHER 	1111
312            #public static final int 	REF 	2006
313            #public static final int 	ROWID 	-8
314            #public static final int 	SQLXML 	2009
315            #public static final int 	STRUCT 	2002
316            #public static final int 	VARBINARY 	-3
317
318        return tuple(map(map_type, range(1, self.columns + 1)))
319
320    def fetchall(self):
321        return list(iter(self.fetchone, None))
322
323    def close(self):
324        try:
325            self.rs.close()
326        except Exception as e:
327            log.exception('Ignored error')
328        try:
329            self.statement.close()
330        except Exception as e:
331            log.exception('Ignored error')
332
333    def nextset(self):
334        raise ProgrammingError("Not Implemented")
335