From db8eb922a8d63e6565cd6a9933d716541f129d50 Mon Sep 17 00:00:00 2001 From: HenryNebula <22852427+HenryNebula@users.noreply.github.com> Date: Sun, 26 Apr 2026 12:05:22 -0400 Subject: [PATCH] Add jvm_args parameter to connect() for custom JVM arguments (fixes #90) Users can now pass custom JVM arguments (e.g., -Duser.timezone, -Dfile.encoding=UTF-8) via the jvm_args parameter. These are applied on the first connect() call when the JVM starts. Co-Authored-By: Claude Opus 4.6 --- jaydebeapiarrow/__init__.py | 14 +++++++++++--- test/test_integration.py | 28 ++++++++++++++++++++++++++++ test/test_mock.py | 15 ++++++++++++++- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/jaydebeapiarrow/__init__.py b/jaydebeapiarrow/__init__.py index 9b1f8053..67760721 100644 --- a/jaydebeapiarrow/__init__.py +++ b/jaydebeapiarrow/__init__.py @@ -128,9 +128,12 @@ def _handle_sql_exception_jpype(): reraise(exc_type, exc_info[1], exc_info[2]) -def _jdbc_connect_jpype(jclassname, url, driver_args, jars, libs): +def _jdbc_connect_jpype(jclassname, url, driver_args, jars, libs, jvm_args=None): import jpype if not _is_jvm_started(): + args = [] + if jvm_args: + args.extend(jvm_args) class_path = [] if jars: class_path.extend(jars) @@ -379,7 +382,7 @@ def TimestampFromTicks(ticks): return Timestamp(*time.localtime(ticks)[:6]) # DB-API 2.0 Module Interface connect constructor -def connect(jclassname, url, driver_args=None, jars=None, libs=None): +def connect(jclassname, url, driver_args=None, jars=None, libs=None, jvm_args=None): """Open a connection to a database using a JDBC driver and return a Connection instance. @@ -395,6 +398,11 @@ def connect(jclassname, url, driver_args=None, jars=None, libs=None): jars: Jar filename or sequence of filenames for the JDBC driver libs: Dll/so filenames or sequence of dlls/sos used as shared library by the JDBC driver + jvm_args: Sequence of additional JVM arguments (e.g. + ['-Duser.timezone=America/Toronto', + '-Dfile.encoding=UTF-8']). Only used on the first + connect() call when the JVM is started. Ignored on + subsequent calls since the JVM cannot be restarted. """ if isinstance(driver_args, str): driver_args = [ driver_args ] @@ -410,7 +418,7 @@ def connect(jclassname, url, driver_args=None, jars=None, libs=None): libs = [ libs ] else: libs = [] - jconn = _jdbc_connect(jclassname, url, driver_args, jars, libs) + jconn = _jdbc_connect(jclassname, url, driver_args, jars, libs, jvm_args) return Connection(jconn, jclassname) # DB-API 2.0 Connection Object diff --git a/test/test_integration.py b/test/test_integration.py index c9242f0d..3f369f4e 100644 --- a/test/test_integration.py +++ b/test/test_integration.py @@ -2007,3 +2007,31 @@ def test_hsqldb_jar_path_with_spaces(self): ) self.assertTrue(result.stdout.strip().startswith('OK'), f'Connection failed: {result.stdout}\n{result.stderr}') + + +class JvmArgsTest(unittest.TestCase): + """Test that jvm_args parameter passes custom JVM arguments on first connect.""" + + _TEST_PROP = 'jaydebeapiarrow.test.jvmargs' + + def setUp(self): + import jpype + if jpype.isJVMStarted(): + self.skipTest("JVM already started; jvm_args only takes effect on first connect") + + def test_jvm_args_sets_system_property(self): + """jvm_args should set JVM system properties during startup.""" + import jpype + import glob + mock_jars = glob.glob(os.path.join(_THIS_DIR, 'mock-jars', 'mockdriver*.jar')) + c = jaydebeapiarrow.connect( + 'org.jaydebeapi.mockdriver.MockDriver', + 'jdbc:jaydebeapi://dummyurl', + jars=mock_jars, + jvm_args=[f'-D{self._TEST_PROP}=hello_from_jvm_args'] + ) + try: + prop_value = jpype.java.lang.System.getProperty(self._TEST_PROP) + self.assertEqual(prop_value, 'hello_from_jvm_args') + finally: + c.close() diff --git a/test/test_mock.py b/test/test_mock.py index c45843d4..f2daea38 100644 --- a/test/test_mock.py +++ b/test/test_mock.py @@ -1248,7 +1248,6 @@ def test_rollback_called_when_autocommit_disabled(self): self.conn.jconn.mockAutoCommit(False) self.conn.rollback() - def test_lastrowid_exists_and_is_none(self): """PEP-249: lastrowid attribute must exist on cursor (fixes #84).""" with self.conn.cursor() as cursor: @@ -1273,6 +1272,20 @@ def test_lastrowid_none_after_executemany(self): """lastrowid should be None after executemany (mock driver limitation: skip).""" self.skipTest("Mock driver executeBatch returns None; covered by integration test") + # --- JVM args parameter test (issue #90) --- + + def test_connect_accepts_jvm_args(self): + """connect() should accept jvm_args parameter without error.""" + import jpype + # The JVM is already started by setUp, so jvm_args won't take effect, + # but the parameter should be accepted without raising TypeError. + conn = jaydebeapiarrow.connect( + 'org.jaydebeapi.mockdriver.MockDriver', + 'jdbc:jaydebeapi://dummyurl', + jvm_args=['-Duser.timezone=UTC'] + ) + conn.close() + class JarPathSpacesTest(unittest.TestCase): """Tests for JAR file paths containing spaces (issue #86).