Skip to content

Commit 48b7d3d

Browse files
authored
FQE-1715 - Microsoft Access handler error (#11825)
1 parent bde93d5 commit 48b7d3d

8 files changed

Lines changed: 571 additions & 112 deletions

File tree

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,32 @@
1-
# Microsoft Access Handler
2-
3-
This is the implementation of the Microsoft Access handler for MindsDB.
4-
5-
## Microsoft Access
6-
Microsoft Access is a pseudo-relational database engine from Microsoft. It is part of the Microsoft Office suite of applications that also includes Word, Outlook and Excel, among others. Access is also available for purchase as a stand-alone product. Access uses the Jet Database Engine for data storage.
7-
https://www.techopedia.com/definition/1218/microsoft-access
8-
9-
## Implementation
10-
This handler was implemented using `pyodbc`, the Python ODBC bridge.
11-
12-
The only required argument to establish a connection is `db_file`. This points to the database file that the connection is to be made to.
13-
14-
## Usage
15-
In order to make use of this handler and connect to an Access database in MindsDB, the following syntax can be used,
16-
~~~~sql
17-
CREATE DATABASE access_datasource
18-
WITH
19-
engine='access',
20-
parameters={
21-
"db_file":"C:\\Users\\minurap\\Documents\\example_db.accdb"
22-
};
23-
~~~~
24-
25-
Now, you can use this established connection to query your database as follows,
26-
~~~~sql
27-
SELECT * FROM access_datasource.example_tbl
1+
# Microsoft Access Handler
2+
3+
This is the implementation of the Microsoft Access handler for MindsDB.
4+
5+
## Microsoft Access
6+
Microsoft Access is a pseudo-relational database engine from Microsoft. It is part of the Microsoft Office suite of applications that also includes Word, Outlook and Excel, among others. Access is also available for purchase as a stand-alone product. Access uses the Jet Database Engine for data storage.
7+
https://www.techopedia.com/definition/1218/microsoft-access
8+
9+
## Implementation
10+
This handler was implemented using `pyodbc`, the Python ODBC bridge.
11+
12+
**Platform Requirements:**
13+
- **Windows Only**: This handler requires the Microsoft Access ODBC driver, which is only available on Windows operating systems.
14+
- The Microsoft Access Driver (*.mdb, *.accdb) must be installed on the system.
15+
16+
The only required argument to establish a connection is `db_file`. This points to the database file that the connection is to be made to.
17+
18+
## Usage
19+
In order to make use of this handler and connect to an Access database in MindsDB, the following syntax can be used,
20+
~~~~sql
21+
CREATE DATABASE access_datasource
22+
WITH
23+
engine='access',
24+
parameters={
25+
"db_file":"C:\\Users\\minurap\\Documents\\example_db.accdb"
26+
};
27+
~~~~
28+
29+
Now, you can use this established connection to query your database as follows,
30+
~~~~sql
31+
SELECT * FROM access_datasource.example_tbl
2832
~~~~
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
__title__ = 'MindsDB Microsoft Access handler'
2-
__package_name__ = 'mindsdb_access_handler'
3-
__version__ = '0.0.1'
1+
__title__ = "MindsDB Microsoft Access handler"
2+
__package_name__ = "mindsdb_access_handler"
3+
__version__ = "0.0.1"
44
__description__ = "MindsDB handler for Microsoft Access"
5-
__author__ = 'Minura Punchihewa'
6-
__github__ = 'https://github.com/mindsdb/mindsdb'
7-
__pypi__ = 'https://pypi.org/project/mindsdb/'
8-
__license__ = 'MIT'
9-
__copyright__ = 'Copyright 2022 - MindsDB'
5+
__author__ = "Minura Punchihewa"
6+
__github__ = "https://github.com/mindsdb/mindsdb"
7+
__pypi__ = "https://pypi.org/project/mindsdb/"
8+
__license__ = "MIT"
9+
__copyright__ = "Copyright 2025 - MindsDB"

mindsdb/integrations/handlers/access_handler/access_handler.py

Lines changed: 44 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
11
from typing import Optional
2+
import platform
23

34
import pandas as pd
4-
import pyodbc
55

66
from mindsdb_sql_parser import parse_sql
7-
from mindsdb.utilities.render.sqlalchemy_render import SqlalchemyRender
8-
from sqlalchemy_access.base import AccessDialect
9-
from mindsdb.integrations.libs.base import DatabaseHandler
10-
117
from mindsdb_sql_parser.ast.base import ASTNode
12-
138
from mindsdb.utilities import log
9+
from mindsdb.utilities.render.sqlalchemy_render import SqlalchemyRender
10+
from mindsdb.integrations.libs.base import DatabaseHandler
1411
from mindsdb.integrations.libs.response import (
1512
HandlerStatusResponse as StatusResponse,
1613
HandlerResponse as Response,
17-
RESPONSE_TYPE
14+
RESPONSE_TYPE,
1815
)
1916

17+
try:
18+
import pyodbc
19+
from sqlalchemy_access.base import AccessDialect
20+
21+
IMPORT_ERROR = None
22+
except ImportError as e:
23+
pyodbc = None
24+
AccessDialect = None
25+
IMPORT_ERROR = e
26+
2027
logger = log.getLogger(__name__)
2128

2229

@@ -25,7 +32,7 @@ class AccessHandler(DatabaseHandler):
2532
This handler handles connection and execution of the Microsoft Access statements.
2633
"""
2734

28-
name = 'access'
35+
name = "access"
2936

3037
def __init__(self, name: str, connection_data: Optional[dict], **kwargs):
3138
"""
@@ -37,7 +44,7 @@ def __init__(self, name: str, connection_data: Optional[dict], **kwargs):
3744
"""
3845
super().__init__(name)
3946
self.parser = parse_sql
40-
self.dialect = 'access'
47+
self.dialect = "access"
4148
self.connection_data = connection_data
4249
self.kwargs = kwargs
4350

@@ -48,18 +55,29 @@ def __del__(self):
4855
if self.is_connected is True:
4956
self.disconnect()
5057

51-
def connect(self) -> StatusResponse:
58+
def connect(self):
5259
"""
5360
Set up the connection required by the handler.
5461
Returns:
55-
HandlerStatusResponse
62+
pyodbc.Connection: A connection object to the Access database.
5663
"""
57-
5864
if self.is_connected is True:
5965
return self.connection
6066

67+
if IMPORT_ERROR is not None:
68+
raise RuntimeError(
69+
f"Microsoft Access handler requires pyodbc and sqlalchemy-access packages. "
70+
f"Install them with: pip install pyodbc sqlalchemy-access. Error: {IMPORT_ERROR}"
71+
)
72+
73+
if platform.system() != "Windows":
74+
raise RuntimeError(
75+
"Microsoft Access handler is only supported on Windows. "
76+
"The Microsoft Access ODBC driver is not available on other operating systems."
77+
)
78+
6179
self.connection = pyodbc.connect(
62-
r'Driver={Microsoft Access Driver (*.mdb, *.accdb)};DBQ=' + self.connection_data['db_file']
80+
r"Driver={Microsoft Access Driver (*.mdb, *.accdb)};DBQ=" + self.connection_data["db_file"]
6381
)
6482
self.is_connected = True
6583

@@ -69,7 +87,6 @@ def disconnect(self):
6987
"""
7088
Close any existing connections.
7189
"""
72-
7390
if self.is_connected is False:
7491
return
7592

@@ -83,15 +100,14 @@ def check_connection(self) -> StatusResponse:
83100
Returns:
84101
HandlerStatusResponse
85102
"""
86-
87103
response = StatusResponse(False)
88104
need_to_close = self.is_connected is False
89105

90106
try:
91107
self.connect()
92108
response.success = True
93109
except Exception as e:
94-
logger.error(f'Error connecting to SQLite {self.connection_data["db_file"]}, {e}!')
110+
logger.error(f"Error connecting to Microsoft Access database {self.connection_data['db_file']}, {e}!")
95111
response.error_message = str(e)
96112
finally:
97113
if response.success is True and need_to_close:
@@ -109,7 +125,6 @@ def native_query(self, query: str) -> StatusResponse:
109125
Returns:
110126
HandlerResponse
111127
"""
112-
113128
need_to_close = self.is_connected is False
114129

115130
connection = self.connect()
@@ -120,21 +135,15 @@ def native_query(self, query: str) -> StatusResponse:
120135
if result:
121136
response = Response(
122137
RESPONSE_TYPE.TABLE,
123-
data_frame=pd.DataFrame.from_records(
124-
result,
125-
columns=[x[0] for x in cursor.description]
126-
)
138+
data_frame=pd.DataFrame.from_records(result, columns=[x[0] for x in cursor.description]),
127139
)
128140

129141
else:
130142
response = Response(RESPONSE_TYPE.OK)
131143
connection.commit()
132144
except Exception as e:
133-
logger.error(f'Error running query: {query} on {self.connection_data["db_file"]}!')
134-
response = Response(
135-
RESPONSE_TYPE.ERROR,
136-
error_message=str(e)
137-
)
145+
logger.error(f"Error running query: {query} on {self.connection_data['db_file']}!")
146+
response = Response(RESPONSE_TYPE.ERROR, error_message=str(e))
138147

139148
if need_to_close is True:
140149
self.disconnect()
@@ -150,6 +159,11 @@ def query(self, query: ASTNode) -> StatusResponse:
150159
Returns:
151160
HandlerResponse
152161
"""
162+
if IMPORT_ERROR is not None:
163+
raise RuntimeError(
164+
f"Microsoft Access handler requires pyodbc and sqlalchemy-access packages. "
165+
f"Install them with: pip install pyodbc sqlalchemy-access. Error: {IMPORT_ERROR}"
166+
)
153167

154168
renderer = SqlalchemyRender(AccessDialect)
155169
query_str = renderer.get_string(query, with_failback=True)
@@ -161,15 +175,11 @@ def get_tables(self) -> StatusResponse:
161175
Returns:
162176
HandlerResponse
163177
"""
164-
165178
connection = self.connect()
166179
with connection.cursor() as cursor:
167-
df = pd.DataFrame([table.table_name for table in cursor.tables(tableType='Table')], columns=['table_name'])
180+
df = pd.DataFrame([table.table_name for table in cursor.tables(tableType="Table")], columns=["table_name"])
168181

169-
response = Response(
170-
RESPONSE_TYPE.TABLE,
171-
df
172-
)
182+
response = Response(RESPONSE_TYPE.TABLE, df)
173183

174184
return response
175185

@@ -181,17 +191,13 @@ def get_columns(self, table_name: str) -> StatusResponse:
181191
Returns:
182192
HandlerResponse
183193
"""
184-
185194
connection = self.connect()
186195
with connection.cursor() as cursor:
187196
df = pd.DataFrame(
188197
[(column.column_name, column.type_name) for column in cursor.columns(table=table_name)],
189-
columns=['column_name', 'data_type']
198+
columns=["column_name", "data_type"],
190199
)
191200

192-
response = Response(
193-
RESPONSE_TYPE.TABLE,
194-
df
195-
)
201+
response = Response(RESPONSE_TYPE.TABLE, df)
196202

197203
return response

mindsdb/integrations/handlers/access_handler/connection_args.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

66
connection_args = OrderedDict(
77
db_file={
8-
'type': ARG_TYPE.STR,
9-
'description': 'The database file where the data will be stored.'
8+
"type": ARG_TYPE.PATH,
9+
"description": "The full path to the Microsoft Access database file (.mdb or .accdb). On Windows, use absolute paths like C:\\Users\\username\\Documents\\database.accdb",
10+
"required": True,
11+
"label": "Database File Path",
1012
}
1113
)
1214

13-
connection_args_example = OrderedDict(
14-
db_file='C:\\Users\\minurap\\Documents\\example_db.accdb'
15-
)
15+
connection_args_example = OrderedDict(db_file="C:\\Users\\minurap\\Documents\\example_db.accdb")
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
pyodbc
2-
sqlalchemy-access
1+
pyodbc>=5.0.0; sys_platform == 'win32'
2+
sqlalchemy-access>=2.0.0; sys_platform == 'win32'

mindsdb/integrations/handlers/access_handler/tests/__init__.py

Whitespace-only changes.

mindsdb/integrations/handlers/access_handler/tests/test_access_handler.py

Lines changed: 0 additions & 32 deletions
This file was deleted.

0 commit comments

Comments
 (0)