diff --git a/c/driver_manager/adbc_driver_manager_driver_loading.cc b/c/driver_manager/adbc_driver_manager_driver_loading.cc index 632184b580..5c2028027a 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 703eddaf9d..2ad571676b 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 823b862bb1..8dd58c4ee0 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 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 632184b580..5c2028027a 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 703eddaf9d..2ad571676b 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 174285aae9..a7cb8d9980 100644 --- a/python/adbc_driver_manager/tests/test_profile.py +++ b/python/adbc_driver_manager/tests/test_profile.py @@ -368,6 +368,21 @@ def test_version_invalid(tmp_path, monkeypatch) -> None: 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 @@ def test_load_driver_manifest(tmp_path, monkeypatch) -> None: 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))