Skip to content

Commit 19e9274

Browse files
authored
improve columnMetaData (#57)
* improve columnMetaData * reformat
1 parent 76cb84c commit 19e9274

File tree

10 files changed

+212
-147
lines changed

10 files changed

+212
-147
lines changed

local_data_api/resources/jdbc/__init__.py

+27-24
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,30 @@ def __init__(self, connection: Connection, transaction_id: Optional[str] = None)
5454
self.autocommit: bool = True
5555
super().__init__(connection, transaction_id)
5656

57+
def create_column_metadata_set(
58+
self, cursor: jaydebeapi.Cursor
59+
) -> List[ColumnMetadata]:
60+
meta = getattr(cursor, '_meta')
61+
return [
62+
ColumnMetadata(
63+
arrayBaseColumnType=0,
64+
isAutoIncrement=meta.isAutoIncrement(i),
65+
isCaseSensitive=meta.isCaseSensitive(i),
66+
isCurrency=meta.isCurrency(i),
67+
isSigned=meta.isSigned(i),
68+
label=meta.getColumnLabel(i),
69+
name=meta.getColumnName(i),
70+
nullable=meta.isNullable(i),
71+
precision=meta.getPrecision(i),
72+
scale=meta.getScale(i),
73+
schema=meta.getSchemaName(i),
74+
tableName=meta.getTableName(i),
75+
type=meta.getColumnType(i),
76+
typeName=meta.getColumnTypeName(i),
77+
)
78+
for i in range(1, meta.getColumnCount() + 1)
79+
]
80+
5781
def autocommit_off(self, cursor: jaydebeapi.Cursor) -> None:
5882
self.connection.jconn.setAutoCommit(False)
5983
self.autocommit = False
@@ -96,8 +120,9 @@ def execute(
96120
],
97121
)
98122
if include_result_metadata:
99-
meta = getattr(cursor, '_meta')
100-
response.columnMetadata = create_column_metadata_set(meta)
123+
response.columnMetadata = self.create_column_metadata_set(
124+
cursor
125+
)
101126
return response
102127
else:
103128
rowcount: int = cursor.rowcount
@@ -144,25 +169,3 @@ def create_connection_maker(
144169
{"user": user_name, "password": password},
145170
engine_kwargs['JAR_PATH'],
146171
)
147-
148-
149-
def create_column_metadata_set(meta: Any) -> List[ColumnMetadata]:
150-
return [
151-
ColumnMetadata(
152-
arrayBaseColumnType=0,
153-
isAutoIncrement=meta.isAutoIncrement(i),
154-
isCaseSensitive=meta.isCaseSensitive(i),
155-
isCurrency=meta.isCurrency(i),
156-
isSigned=meta.isSigned(i),
157-
label=meta.getColumnLabel(i),
158-
name=meta.getColumnName(i),
159-
nullable=meta.isNullable(i),
160-
precision=meta.getPrecision(i),
161-
scale=meta.getScale(i),
162-
schema=meta.getSchemaName(i),
163-
tableName=meta.getTableName(i),
164-
type=meta.getColumnType(i),
165-
typeName=meta.getColumnTypeName(i),
166-
)
167-
for i in range(1, meta.getColumnCount() + 1)
168-
]

local_data_api/resources/mysql.py

+33-2
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,49 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING, Any, Dict, Optional
3+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
44

55
import pymysql
6+
from pymysql.constants import FIELD_TYPE
7+
from pymysql.protocol import FieldDescriptorPacket
68
from sqlalchemy.dialects import mysql
79

10+
from local_data_api.models import ColumnMetadata
811
from local_data_api.resources.resource import Resource, register_resource_type
912

1013
if TYPE_CHECKING: # pragma: no cover
11-
from local_data_api.resources.resource import ConnectionMaker
14+
from local_data_api.resources.resource import ConnectionMaker, Cursor
15+
16+
FIELD_TYPE_MAP: Dict[int, str] = {
17+
getattr(FIELD_TYPE, k): k for k in dir(FIELD_TYPE) if not k.startswith('_')
18+
}
19+
20+
21+
def create_column_metadata(
22+
field_descriptor_packet: FieldDescriptorPacket,
23+
) -> ColumnMetadata:
24+
return ColumnMetadata(
25+
arrayBaseColumnType=0,
26+
isAutoIncrement=False,
27+
isCaseSensitive=False,
28+
isCurrency=False,
29+
isSigned=False,
30+
label=field_descriptor_packet.name,
31+
name=field_descriptor_packet.org_name,
32+
nullable=1 if field_descriptor_packet.flags % 2 == 0 else 0,
33+
precision=field_descriptor_packet.get_column_length(),
34+
scale=field_descriptor_packet.scale,
35+
schema=None,
36+
tableName=field_descriptor_packet.table_name,
37+
type=None, # JDBC Type unsupported
38+
typeName=None, # JDBC TypeName unsupported
39+
)
1240

1341

1442
@register_resource_type
1543
class MySQL(Resource):
44+
def create_column_metadata_set(self, cursor: Cursor) -> List[ColumnMetadata]:
45+
return [create_column_metadata(f) for f in getattr(cursor, '_result').fields]
46+
1647
DIALECT = mysql.dialect(paramstyle='named')
1748

1849
@classmethod

local_data_api/resources/postgres.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING, Any, Dict, Optional
3+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
44

55
import psycopg2
66
from sqlalchemy.dialects import postgresql
77

8+
from local_data_api.models import ColumnMetadata
89
from local_data_api.resources.resource import Resource, register_resource_type
910

1011
if TYPE_CHECKING: # pragma: no cover
11-
from local_data_api.resources.resource import ConnectionMaker
12+
from local_data_api.resources.resource import ConnectionMaker, Cursor
1213

1314

1415
@register_resource_type
1516
class PostgresSQL(Resource):
17+
def create_column_metadata_set(
18+
self, cursor: Cursor
19+
) -> List[ColumnMetadata]: # pragma: no cover
20+
raise NotImplementedError
21+
1622
DIALECT = postgresql.dialect(paramstyle='named')
1723

1824
@classmethod

local_data_api/resources/resource.py

+49-28
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
import string
66
from abc import ABC, abstractmethod
77
from dataclasses import dataclass
8+
from enum import Enum
89
from hashlib import sha1
910
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Pattern, Tuple, Type, Union
1011

12+
from pydantic.main import BaseModel
1113
from sqlalchemy import text
1214
from sqlalchemy.engine import Dialect
1315
from sqlalchemy.exc import ArgumentError, CompileError
@@ -222,31 +224,46 @@ def get_resource(
222224
return meta.resource_type(connection, transaction_id)
223225

224226

225-
def create_column_metadata(
226-
name: str,
227-
type_code: int,
228-
display_size: Optional[int],
229-
internal_size: int,
230-
precision: int,
231-
scale: int,
232-
null_ok: bool,
233-
) -> ColumnMetadata:
234-
return ColumnMetadata(
235-
arrayBaseColumnType=0,
236-
isAutoIncrement=False,
237-
isCaseSensitive=False,
238-
isCurrency=False,
239-
isSigned=False,
240-
label=name,
241-
name=name,
242-
nullable=1 if null_ok else 0,
243-
precision=precision,
244-
scale=scale,
245-
schema=None,
246-
tableName=None,
247-
type=None,
248-
typeName=None,
249-
)
227+
class JDBCType(Enum):
228+
BIT = -7
229+
TINYINT = -6
230+
SMALLINT = 5
231+
INTEGER = 4
232+
BIGINT = -5
233+
FLOAT = 6
234+
REAL = 7
235+
DOUBLE = 8
236+
NUMERIC = 2
237+
DECIMAL = 3
238+
CHAR = 1
239+
VARCHAR = 12
240+
LONGVARCHAR = -1
241+
DATE = 91
242+
TIME = 92
243+
TIMESTAMP = 93
244+
BINARY = -2
245+
VARBINARY = -3
246+
LONGVARBINARY = -4
247+
NULL = 0
248+
OTHER = 1111
249+
JAVA_OBJECT = 2000
250+
DISTINCT = 2001
251+
STRUCT = 2002
252+
ARRAY = 2003
253+
BLOB = 2004
254+
CLOB = 2005
255+
REF = 2006
256+
DATALINK = 70
257+
BOOLEAN = 16
258+
ROWID = -8
259+
NCHAR = -15
260+
NVARCHAR = -9
261+
LONGNVARCHAR = -16
262+
NCLOB = 2011
263+
SQLXML = 2009
264+
REF_CURSOR = 2012
265+
TIME_WITH_TIMEZONE = 2013
266+
TIMESTAMP_WITH_TIMEZONE = 2014
250267

251268

252269
class Resource(ABC):
@@ -309,6 +326,10 @@ def create_transaction_id() -> str:
309326
for _ in range(TRANSACTION_ID_LENGTH)
310327
)
311328

329+
@abstractmethod
330+
def create_column_metadata_set(self, cursor: Cursor) -> List[ColumnMetadata]:
331+
raise NotImplementedError
332+
312333
def close(self) -> None:
313334
self.connection.close()
314335
if self.transaction_id in CONNECTION_POOL:
@@ -351,9 +372,9 @@ def execute(
351372
],
352373
)
353374
if include_result_metadata:
354-
response.columnMetadata = [
355-
create_column_metadata(*d) for d in cursor.description
356-
]
375+
response.columnMetadata = self.create_column_metadata_set(
376+
cursor
377+
)
357378
return response
358379
else:
359380
rowcount: int = cursor.rowcount

local_data_api/resources/sqlite.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
from __future__ import annotations
22

33
import sqlite3
4-
from typing import TYPE_CHECKING, Any, Dict, Optional
4+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
55

66
from sqlalchemy.dialects import sqlite
77

8+
from local_data_api.models import ColumnMetadata
89
from local_data_api.resources.resource import Resource, register_resource_type
910

1011
if TYPE_CHECKING: # pragma: no cover
11-
from local_data_api.resources.resource import ConnectionMaker
12+
from local_data_api.resources.resource import ConnectionMaker, Cursor
1213

1314

1415
@register_resource_type
1516
class SQLite(Resource):
17+
def create_column_metadata_set(self, cursor: Cursor) -> List[ColumnMetadata]:
18+
raise NotImplementedError
19+
1620
DIALECT = sqlite.dialect(paramstyle='named')
1721

1822
@classmethod

tests/test_resource/test_jdbc/test_jdbc.py

+4-8
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,7 @@
44
import pytest
55

66
from local_data_api.models import ColumnMetadata
7-
from local_data_api.resources.jdbc import (
8-
JDBC,
9-
attach_thread_to_jvm,
10-
connection_maker,
11-
create_column_metadata_set,
12-
)
7+
from local_data_api.resources.jdbc import JDBC, attach_thread_to_jvm, connection_maker
138

149

1510
class DummyJDBC(JDBC):
@@ -78,8 +73,9 @@ def test_create_column_metadata_set(mocker):
7873
mock_meta.getColumnType.side_effect = [5, 6]
7974
mock_meta.getColumnTypeName.side_effect = ['i', 'j']
8075
mock_meta.getColumnCount.return_value = 2
81-
82-
assert create_column_metadata_set(mock_meta) == [
76+
mock_cursor = mocker.Mock()
77+
mock_cursor._meta = mock_meta
78+
assert DummyJDBC(mocker.Mock()).create_column_metadata_set(mock_cursor) == [
8379
ColumnMetadata(
8480
arrayBaseColumnType=0,
8581
isAutoIncrement=True,

tests/test_resource/test_jdbc/test_mysql.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,7 @@ def test_execute_select_with_include_metadata(mocked_connection, mocked_cursor,
124124
mocked_cursor.description = (1, 2, 3, 4, 5, 6, 7), (8, 9, 10, 11, 12, 13, 14)
125125
mocked_cursor.fetchall.side_effect = [((1, 'abc'),)]
126126
dummy = MySQLJDBC(mocked_connection, transaction_id='123')
127-
create_column_metadata_set_mock = mocker.patch(
128-
'local_data_api.resources.jdbc.create_column_metadata_set'
129-
)
127+
dummy.create_column_metadata_set = create_column_metadata_set_mock = mocker.Mock()
130128
create_column_metadata_set_mock.side_effect = [
131129
[
132130
ColumnMetadata(
@@ -197,7 +195,7 @@ def test_execute_select_with_include_metadata(mocked_connection, mocked_cursor,
197195
],
198196
)
199197

200-
create_column_metadata_set_mock.assert_called_once_with(meta_mock)
198+
create_column_metadata_set_mock.assert_called_once_with(mocked_cursor)
201199
mocked_cursor.execute.assert_has_calls(
202200
[mocker.call('SELECT LAST_INSERT_ID(NULL)'), mocker.call('select * from users')]
203201
)

tests/test_resource/test_jdbc/test_postgres.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,10 @@ def test_execute_select(mocked_connection, mocked_cursor, mocker):
9999

100100

101101
def test_execute_select_with_include_metadata(mocked_connection, mocked_cursor, mocker):
102-
meta_mock = mocker.Mock()
103-
mocked_cursor._meta = meta_mock
104102
mocked_cursor.description = (1, 2, 3, 4, 5, 6, 7), (8, 9, 10, 11, 12, 13, 14)
105103
mocked_cursor.fetchall.side_effect = [((1, 'abc'),)]
106104
dummy = PostgreSQLJDBC(mocked_connection, transaction_id='123')
107-
create_column_metadata_set_mock = mocker.patch(
108-
'local_data_api.resources.jdbc.create_column_metadata_set'
109-
)
105+
dummy.create_column_metadata_set = create_column_metadata_set_mock = mocker.Mock()
110106
create_column_metadata_set_mock.side_effect = [
111107
[
112108
ColumnMetadata(
@@ -177,7 +173,7 @@ def test_execute_select_with_include_metadata(mocked_connection, mocked_cursor,
177173
],
178174
)
179175

180-
create_column_metadata_set_mock.assert_called_once_with(meta_mock)
176+
create_column_metadata_set_mock.assert_called_once_with(mocked_cursor)
181177
mocked_cursor.execute.assert_has_calls([mocker.call('select * from users')])
182178
mocked_cursor.close.assert_called_once_with()
183179

0 commit comments

Comments
 (0)