This is an automated email from the ASF dual-hosted git repository.
amoeba pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git
The following commit(s) were added to refs/heads/main by this push:
new fc6f7d003 feat(c): add source context to manifest and profile parse
errors (#4633)
fc6f7d003 is described below
commit fc6f7d003cdbe5b21739a904485fab80656198c0
Author: Bryce Mecum <[email protected]>
AuthorDate: Fri Jul 31 14:56:21 2026 -0700
feat(c): add source context to manifest and profile parse errors (#4633)
If I have an manifest or profile that's invalid TOML, such as,
```toml
profile_version = 1
driver = not_a_valid_toml_value
```
I get a vague error:
> [Driver Manager] Could not open profile. Error while parsing value:
could not determine value type
It turns out the toml++ library can show the line and column number
where the parse error occurred. This PR adds that context if it's
available so we get a better error:
> [Driver Manager] Could not open profile. Error while parsing value:
could not determine value type (line 2, column 10).
---
.../adbc_driver_manager_driver_loading.cc | 8 +++++
c/driver_manager/adbc_driver_manager_profiles.cc | 8 +++++
c/driver_manager/adbc_driver_manager_test.cc | 40 ++++++++++++++++++++++
.../adbc_driver_manager_driver_loading.cc | 8 +++++
go/adbc/drivermgr/adbc_driver_manager_profiles.cc | 8 +++++
python/adbc_driver_manager/tests/test_profile.py | 32 +++++++++++++++++
6 files changed, 104 insertions(+)
diff --git a/c/driver_manager/adbc_driver_manager_driver_loading.cc
b/c/driver_manager/adbc_driver_manager_driver_loading.cc
index c17956057..9b2c84c97 100644
--- a/c/driver_manager/adbc_driver_manager_driver_loading.cc
+++ b/c/driver_manager/adbc_driver_manager_driver_loading.cc
@@ -163,6 +163,14 @@ AdbcStatusCode LoadDriverManifest(const
std::filesystem::path& driver_manifest,
// differentiate between bad syntax and other I/O error.
std::string message = "Could not open manifest. ";
message += err.what();
+ const auto& src = err.source();
+ if (src.begin) {
+ message += " (line ";
+ message += std::to_string(src.begin.line);
+ message += ", column ";
+ message += std::to_string(src.begin.column);
+ message += ")";
+ }
message += ". Manifest: ";
message += driver_manifest.string();
SetError(error, std::move(message));
diff --git a/c/driver_manager/adbc_driver_manager_profiles.cc
b/c/driver_manager/adbc_driver_manager_profiles.cc
index 703eddaf9..2ad571676 100644
--- a/c/driver_manager/adbc_driver_manager_profiles.cc
+++ b/c/driver_manager/adbc_driver_manager_profiles.cc
@@ -288,6 +288,14 @@ AdbcStatusCode LoadProfileFile(const
std::filesystem::path& profile_path,
} catch (const toml::parse_error& err) {
std::string message = "Could not open profile. ";
message += err.what();
+ const auto& src = err.source();
+ if (src.begin) {
+ message += " (line ";
+ message += std::to_string(src.begin.line);
+ message += ", column ";
+ message += std::to_string(src.begin.column);
+ message += ")";
+ }
message += ". Profile: ";
message += profile_path.string();
SetError(error, std::move(message));
diff --git a/c/driver_manager/adbc_driver_manager_test.cc
b/c/driver_manager/adbc_driver_manager_test.cc
index 3417b90c8..750b7fea7 100644
--- a/c/driver_manager/adbc_driver_manager_test.cc
+++ b/c/driver_manager/adbc_driver_manager_test.cc
@@ -2059,6 +2059,46 @@ TEST_F(ConnectionProfiles, CustomProfileProvider) {
ASSERT_THAT(AdbcDatabaseRelease(&database.value, &error),
IsOkStatus(&error));
}
+TEST_F(ConnectionProfiles, ProfileParseErrorIncludesLocation) {
+ auto filepath = temp_dir / "badprofile.toml";
+ std::ofstream file(filepath);
+ ASSERT_TRUE(file.is_open());
+ file << "profile_version = 1\n"
+ << "driver = unquoted_value\n"
+ << "[Options]\n";
+ file.close();
+
+ adbc_validation::Handle<struct AdbcDatabase> database;
+ ASSERT_THAT(AdbcDatabaseNew(&database.value, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOption(&database.value, "profile",
filepath.string().c_str(),
+ &error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database.value, &error),
+ IsStatus(ADBC_STATUS_INVALID_ARGUMENT, &error));
+ ASSERT_THAT(error.message, ::testing::HasSubstr("line 2"));
+ ASSERT_THAT(error.message, ::testing::HasSubstr("column"));
+ ASSERT_THAT(AdbcDatabaseRelease(&database.value, &error),
IsOkStatus(&error));
+}
+
+TEST_F(DriverManifest, ManifestParseErrorIncludesLocation) {
+ std::ofstream file(temp_dir / "baddriver.toml");
+ ASSERT_TRUE(file.is_open());
+ file << "manifest_version = 1\n"
+ << "[Driver]\n"
+ << "shared = unquoted_value\n";
+ file.close();
+
+ SetDriverPath(temp_dir.string().c_str());
+
+ ASSERT_THAT(AdbcFindLoadDriver("baddriver", nullptr, ADBC_VERSION_1_1_0,
+ ADBC_LOAD_FLAG_DEFAULT, nullptr, &driver,
&error),
+ IsStatus(ADBC_STATUS_INVALID_ARGUMENT, &error));
+ ASSERT_THAT(error.message, ::testing::HasSubstr("line 3"));
+ ASSERT_THAT(error.message, ::testing::HasSubstr("column"));
+
+ UnsetDriverPath();
+}
+
struct DriverUriProfile {
std::string name;
std::string driver;
diff --git a/go/adbc/drivermgr/adbc_driver_manager_driver_loading.cc
b/go/adbc/drivermgr/adbc_driver_manager_driver_loading.cc
index c17956057..9b2c84c97 100644
--- a/go/adbc/drivermgr/adbc_driver_manager_driver_loading.cc
+++ b/go/adbc/drivermgr/adbc_driver_manager_driver_loading.cc
@@ -163,6 +163,14 @@ AdbcStatusCode LoadDriverManifest(const
std::filesystem::path& driver_manifest,
// differentiate between bad syntax and other I/O error.
std::string message = "Could not open manifest. ";
message += err.what();
+ const auto& src = err.source();
+ if (src.begin) {
+ message += " (line ";
+ message += std::to_string(src.begin.line);
+ message += ", column ";
+ message += std::to_string(src.begin.column);
+ message += ")";
+ }
message += ". Manifest: ";
message += driver_manifest.string();
SetError(error, std::move(message));
diff --git a/go/adbc/drivermgr/adbc_driver_manager_profiles.cc
b/go/adbc/drivermgr/adbc_driver_manager_profiles.cc
index 703eddaf9..2ad571676 100644
--- a/go/adbc/drivermgr/adbc_driver_manager_profiles.cc
+++ b/go/adbc/drivermgr/adbc_driver_manager_profiles.cc
@@ -288,6 +288,14 @@ AdbcStatusCode LoadProfileFile(const
std::filesystem::path& profile_path,
} catch (const toml::parse_error& err) {
std::string message = "Could not open profile. ";
message += err.what();
+ const auto& src = err.source();
+ if (src.begin) {
+ message += " (line ";
+ message += std::to_string(src.begin.line);
+ message += ", column ";
+ message += std::to_string(src.begin.column);
+ message += ")";
+ }
message += ". Profile: ";
message += profile_path.string();
SetError(error, std::move(message));
diff --git a/python/adbc_driver_manager/tests/test_profile.py
b/python/adbc_driver_manager/tests/test_profile.py
index 174285aae..a7cb8d998 100644
--- a/python/adbc_driver_manager/tests/test_profile.py
+++ b/python/adbc_driver_manager/tests/test_profile.py
@@ -368,6 +368,21 @@ profile_version = 9001
pass
+def test_parse_error_includes_location(tmp_path, monkeypatch) -> None:
+ # Test that TOML parse errors include line/column information
+ monkeypatch.setenv("ADBC_PROFILE_PATH", str(tmp_path))
+
+ with (tmp_path / "badtoml.toml").open("w") as sink:
+ sink.write("""profile_version = 1
+driver = unquoted_value
+[Options]
+""")
+
+ with pytest.raises(dbapi.ProgrammingError, match=r"line 2.*column"):
+ with dbapi.connect("profile://badtoml"):
+ pass
+
+
def test_reject_malformed(tmp_path, monkeypatch) -> None:
# Test that invalid profiles are rejected
monkeypatch.setenv("ADBC_PROFILE_PATH", str(tmp_path))
@@ -448,6 +463,23 @@ driver = "sqlitemanifest"
assert cursor.fetchone() is not None
+def test_manifest_parse_error_includes_location(tmp_path, monkeypatch) -> None:
+ # Test that TOML parse errors in manifests include line/column information
+ manifest_path = tmp_path / "manifest"
+ manifest_path.mkdir()
+ monkeypatch.setenv("ADBC_DRIVER_PATH", str(manifest_path))
+
+ with (manifest_path / "badmanifest.toml").open("w") as sink:
+ sink.write("""manifest_version = 1
+[Driver]
+shared = unquoted_value
+""")
+
+ with pytest.raises(dbapi.ProgrammingError, match=r"line 3.*column"):
+ with dbapi.connect("badmanifest"):
+ pass
+
+
def test_subdir(monkeypatch, tmp_path) -> None:
# Test that we can search in subdirectories
monkeypatch.setenv("ADBC_PROFILE_PATH", str(tmp_path))