1- #include < vector >
1+ #include < utility >
22
3+ #include " duckdb/common/case_insensitive_map.hpp"
34#include " duckdb/common/exception.hpp"
5+ #include " duckdb/common/exception/binder_exception.hpp"
46#include " duckdb/common/file_system.hpp"
7+ #include " duckdb/common/types.hpp"
58#include " duckdb/common/types/value.hpp"
69
710#include " gsheets_copy.hpp"
811#include " gsheets_utils.hpp"
912
1013#include " sheets/auth_factory.hpp"
1114#include " sheets/client.hpp"
15+ #include " sheets/exception.hpp"
1216#include " sheets/range.hpp"
1317#include " sheets/transport/client_factory.hpp"
1418#include " sheets/types.hpp"
@@ -22,110 +26,66 @@ GSheetCopyFunction::GSheetCopyFunction() : CopyFunction("gsheet") {
2226 copy_to_sink = GSheetWriteSink;
2327}
2428
29+ static std::string GetStringOption (const case_insensitive_map_t <vector<Value>> &options, const std::string &name,
30+ const std::string &default_value = " " ) {
31+ const auto it = options.find (name);
32+ if (it == options.end ()) {
33+ return default_value;
34+ }
35+ std::string err;
36+ Value val;
37+ if (!it->second .back ().DefaultTryCastAs (LogicalType::VARCHAR , val, &err)) {
38+ throw BinderException (name + " option must be VARCHAR" );
39+ }
40+ if (val.IsNull ()) {
41+ throw BinderException (name + " option must not be NULL" );
42+ }
43+ return StringValue::Get (val);
44+ }
45+
46+ // NOTE: the second value in pair is a flag indicating if the value was set by the user
47+ static std::pair<bool , bool > GetBoolOption (const case_insensitive_map_t <vector<Value>> &options,
48+ const std::string &name, bool default_value = false ) {
49+ const auto it = options.find (name);
50+ if (it == options.end ()) {
51+ return std::make_pair (default_value, false );
52+ }
53+ if (it->second .size () != 1 ) {
54+ throw BinderException (name + " option must be a single boolean value" );
55+ }
56+ std::string err;
57+ Value val;
58+ if (!it->second .back ().DefaultTryCastAs (LogicalType::BOOLEAN , val, &err)) {
59+ throw BinderException (name + " option must be a single boolean value" );
60+ }
61+ if (val.IsNull ()) {
62+ throw BinderException (name + " option must be a single boolean value" );
63+ }
64+ return std::make_pair (BooleanValue::Get (val), true );
65+ }
66+
2567unique_ptr<FunctionData> GSheetCopyFunction::GSheetWriteBind (ClientContext &context, CopyFunctionBindInput &input,
2668 const vector<string> &names,
2769 const vector<LogicalType> &sql_types) {
28- string file_path = input.info .file_path ;
2970
71+ string file_path = input.info .file_path ;
3072 auto options = input.info .options ;
3173
32- const auto sheet_opt = options.find (" sheet" );
33- std::string sheet;
34- if (sheet_opt != options.end ()) {
35- string error_msg;
36- Value sheet_val;
37- if (!sheet_opt->second .back ().DefaultTryCastAs (LogicalType::VARCHAR , sheet_val, &error_msg)) {
38- throw BinderException (" sheet option must be a VARCHAR" );
39- }
40- if (sheet_val.IsNull ()) {
41- throw BinderException (" sheet option must be a non-null VARCHAR" );
42- }
43- sheet = StringValue::Get (sheet_val);
44- } else {
45- sheet = " " ;
46- }
74+ auto sheet = GetStringOption (options, " sheet" );
75+ auto range = GetStringOption (options, " range" );
76+ bool overwrite_sheet = GetBoolOption (options, " overwrite_sheet" , true ).first ;
77+ bool overwrite_range = GetBoolOption (options, " overwrite_range" , false ).first ;
78+ bool create_if_not_exists = GetBoolOption (options, " create_if_not_exists" , false ).first ;
4779
48- const auto range_opt = options.find (" range" );
49- std::string range;
50- if (range_opt != options.end ()) {
51- string error_msg;
52- Value range_val;
53- if (!range_opt->second .back ().DefaultTryCastAs (LogicalType::VARCHAR , range_val, &error_msg)) {
54- throw BinderException (" range option must be a VARCHAR" );
55- }
56- if (range_val.IsNull ()) {
57- throw BinderException (" range option must be a non-null VARCHAR" );
58- }
59- range = StringValue::Get (range_val);
60- } else {
61- range = " " ;
62- }
80+ auto header_result = GetBoolOption (options, " header" , true );
81+ bool header = header_result.second ? header_result.first : (overwrite_range || overwrite_sheet);
6382
64- const auto overwrite_sheet_opt = options.find (" overwrite_sheet" );
65- bool overwrite_sheet;
66- if (overwrite_sheet_opt != options.end ()) {
67- if (overwrite_sheet_opt->second .size () != 1 ) {
68- throw BinderException (" overwrite_sheet option must be a single boolean value" );
69- }
70- string error_msg;
71- Value overwrite_sheet_bool_val;
72- if (!overwrite_sheet_opt->second .back ().DefaultTryCastAs (LogicalType::BOOLEAN , overwrite_sheet_bool_val,
73- &error_msg)) {
74- throw BinderException (" overwrite_sheet option must be a single boolean value" );
75- }
76- if (overwrite_sheet_bool_val.IsNull ()) {
77- throw BinderException (" overwrite_sheet option must be a single boolean value" );
78- }
79- overwrite_sheet = BooleanValue::Get (overwrite_sheet_bool_val);
80- } else {
81- overwrite_sheet = true ; // Default to overwrite_sheet to maintain prior behavior
82- }
83-
84- const auto overwrite_range_opt = options.find (" overwrite_range" );
85- bool overwrite_range;
86- if (overwrite_range_opt != options.end ()) {
87- if (overwrite_range_opt->second .size () != 1 ) {
88- throw BinderException (" overwrite_range option must be a single boolean value" );
89- }
90- string error_msg;
91- Value overwrite_range_bool_val;
92- if (!overwrite_range_opt->second .back ().DefaultTryCastAs (LogicalType::BOOLEAN , overwrite_range_bool_val,
93- &error_msg)) {
94- throw BinderException (" overwrite_range option must be a single boolean value" );
95- }
96- if (overwrite_range_bool_val.IsNull ()) {
97- throw BinderException (" overwrite_range option must be a single boolean value" );
98- }
99- overwrite_range = BooleanValue::Get (overwrite_range_bool_val);
100- } else {
101- overwrite_range = false ;
102- }
103-
104- const auto header_opt = options.find (" header" );
105- bool header;
106- if (header_opt != options.end ()) {
107- if (header_opt->second .size () != 1 ) {
108- throw BinderException (" header option must be a single boolean value" );
109- }
110- string error_msg;
111- Value header_bool_val;
112- if (!header_opt->second .back ().DefaultTryCastAs (LogicalType::BOOLEAN , header_bool_val, &error_msg)) {
113- throw BinderException (" header option must be a single boolean value" );
114- }
115- if (header_bool_val.IsNull ()) {
116- throw BinderException (" header option must be a single boolean value" );
117- }
118- header = BooleanValue::Get (header_bool_val);
119- } else {
120- header = true ;
121- // If we are in the append case, default to no header instead.
122- if (!overwrite_sheet && !overwrite_range) {
123- header = false ;
124- }
83+ if (create_if_not_exists && sheet.empty ()) {
84+ throw BinderException (" Must provide sheet name" );
12585 }
12686
12787 return make_uniq<GSheetWriteBindData>(file_path, sql_types, names, sheet, range, overwrite_sheet, overwrite_range,
128- header);
88+ create_if_not_exists, header);
12989}
13090
13191unique_ptr<GlobalFunctionData> GSheetCopyFunction::GSheetWriteInitializeGlobal (ClientContext &context,
@@ -154,6 +114,16 @@ unique_ptr<GlobalFunctionData> GSheetCopyFunction::GSheetWriteInitializeGlobal(C
154114 sheet_name = sheet.properties .title ;
155115 }
156116
117+ // Create sheet if not exist (if enabled)
118+ if (options.create_if_not_exists ) {
119+ try {
120+ auto sheet = client.Spreadsheets (spreadsheet_id).GetSheetByName (sheet_name);
121+ // sheet already exists so we need to ignore this instruction from the user
122+ } catch (sheets::SheetNotFoundException &_) {
123+ client.Spreadsheets (spreadsheet_id).CreateSheet (sheet_name);
124+ }
125+ }
126+
157127 if (!options.range .empty ()) {
158128 sheet_range = options.range ;
159129 } else {
0 commit comments