Skip to content

Commit 35e9ddc

Browse files
committed
2 parents 1dd2d34 + d770b53 commit 35e9ddc

File tree

5 files changed

+34
-16
lines changed

5 files changed

+34
-16
lines changed

docs/pages/index.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,6 @@ This will also require an additional API request approximately every 30 minutes.
143143

144144
- DuckDB WASM is not (yet) supported.
145145
- Google Sheets has a limit of 10,000,000 cells per spreadsheet.
146-
- Writing data to a sheet starting from a cell other than A1 is not yet supported.
147146
- Sheets must already exist to COPY TO them.
148147

149148
## Support

src/gsheets_auth.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ namespace duckdb
9393
auto filepath = (input.options.find(filepath_key)->second).ToString();
9494

9595
std::ifstream ifs(filepath);
96+
if (!ifs.is_open()) {
97+
throw IOException("Could not open JSON key file at: " + filepath);
98+
}
9699
json credentials_file = json::parse(ifs);
97100
std::string email = credentials_file["client_email"].get<std::string>();
98101
std::string secret = credentials_file["private_key"].get<std::string>();

src/gsheets_read.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -218,16 +218,17 @@ unique_ptr<FunctionData> ReadSheetBind(ClientContext &context, TableFunctionBind
218218
json cleanJson = parseJson(bind_data->response);
219219
SheetData sheet_data = getSheetData(cleanJson);
220220

221-
// Prefering early return style to reduce nesting
221+
// Throw error ourselves to give user a better error message
222222
if (sheet_data.values.empty()) {
223-
return bind_data;
223+
throw duckdb::InvalidInputException("Sheet %s is empty", sheet_name);
224224
}
225+
225226
idx_t start_index = header ? 1 : 0;
226-
if (start_index >= sheet_data.values.size()) {
227-
return bind_data;
228-
}
229227

230-
const auto& first_data_row = sheet_data.values[start_index];
228+
// Use empty row for first row if results are header-only
229+
const std::vector<string> empty_row = {};
230+
const auto& first_data_row = start_index >= sheet_data.values.size() ? empty_row : sheet_data.values[start_index];
231+
231232
// If we have a header, we want the width of the result to be the max of:
232233
// the width of the header row
233234
// or the width of the first row of data

src/gsheets_utils.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -111,18 +111,18 @@ json parseJson(const std::string& json_str) {
111111

112112
SheetData getSheetData(const json& j) {
113113
SheetData result;
114-
if (j.contains("range") && j.contains("majorDimension") && j.contains("values")) {
114+
if (j.contains("range") && j.contains("majorDimension")) {
115115
result.range = j["range"].get<std::string>();
116116
result.majorDimension = j["majorDimension"].get<std::string>();
117-
result.values = j["values"].get<std::vector<std::vector<std::string>>>();
117+
// NOTE: no need to hard fail on empty sheet values. We can handle this more naturally further down the call chain.
118+
// Just default to empty 2D vec.
119+
result.values = j.contains("values") ? j["values"].get<std::vector<std::vector<std::string>>>() : std::vector<std::vector<std::string>>{};
118120
} else if (j.contains("error")) {
119121
string message = j["error"]["message"].get<std::string>();
120-
int code = j["error"]["code"].get<int>();
121-
throw std::runtime_error("Google Sheets API error: " + std::to_string(code) + " - " + message);
122-
} else {
123-
std::cerr << "JSON does not contain expected fields" << std::endl;
124-
std::cerr << "Raw JSON string: " << j.dump() << std::endl;
125-
throw;
122+
int code = j["error"]["code"].get<int>();
123+
throw std::runtime_error("Google Sheets API error: " + std::to_string(code) + " - " + message);
124+
} else {
125+
throw duckdb::InvalidInputException("JSON does not contain expected fields.\nRaw JSON string: %s", j.dump());
126126
}
127127
return result;
128128
}

test/sql/read_gsheet.test

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,11 @@ NULL NULL
109109
Archie 99.0
110110

111111
# Test single value from range
112-
# NOTE: *must* use `header=false` to avoid uncaught bind error
112+
query I
113+
FROM read_gsheet('11QdEasMWbETbFVxry-SsD8jVcdYIT1zBQszcF84MdE8', sheet='Sheet1', range='A2');
114+
----
115+
116+
# Test single value from range (no headers)
113117
query I
114118
FROM read_gsheet('11QdEasMWbETbFVxry-SsD8jVcdYIT1zBQszcF84MdE8', sheet='Sheet1', range='A2', header=false);
115119
----
@@ -168,6 +172,17 @@ FROM read_gsheet('https://docs.google.com/spreadsheets/d/11QdEasMWbETbFVxry-SsD8
168172
woot blah NULL should get this!
169173
more wooting more blah NULL should get this!
170174

175+
# Header only results
176+
query II
177+
FROM read_gsheet('11QdEasMWbETbFVxry-SsD8jVcdYIT1zBQszcF84MdE8', sheet='62-header_only');
178+
----
179+
180+
# Empty sheet
181+
statement error
182+
FROM read_gsheet('11QdEasMWbETbFVxry-SsD8jVcdYIT1zBQszcF84MdE8', sheet='62-empty');
183+
----
184+
Invalid Input Error: Sheet 62-empty is empty
185+
171186
# Drop the secret
172187
statement ok
173188
drop secret test_secret;

0 commit comments

Comments
 (0)