diff --git a/docs/extensions.md b/docs/extensions.md index 7b2471e6..c7a70e9f 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -9,7 +9,9 @@ | `iceberg` | Apache Iceberg support | Installable | | `delta` | Delta Lake support | Installable | | `azure` | Azure Blob Storage connectivity | Installable | +| `excel` | Excel Xlsx file support | Installable | | Community | Various community extensions | Installable (requires configuration) | +| `gsheets` | GSheets from GDrive file support | Installable (requires configuration) | Installing other extensions may work, but they have not been thoroughly tested and are used at your own risk @@ -42,6 +44,11 @@ SELECT duckdb.install_extension('prql', 'community'); SET duckdb.allow_unsigned_extensions = true; ``` +**Note**: Recycling of the DuckDb might be required: +```sql +CALL duckdb.recycle_ddb(); +``` + ## Managing Extensions Installing an extension causes it to be loaded and installed globally for any connection that uses DuckDB. The current list of installed extensions is maintained in the `duckdb.extensions` table. Superusers can use this table to view and manage extensions: diff --git a/docs/secrets.md b/docs/secrets.md index 86fa2af0..7311dd13 100644 --- a/docs/secrets.md +++ b/docs/secrets.md @@ -45,6 +45,20 @@ SELECT duckdb.create_azure_secret( ); ``` +For GSheets secrets you may use: +```sql +SELECT duckdb.create_gsheet_secret( + 'key_file', '/path/to/credential.json' +); +``` +Or +```sql +SELECT duckdb.create_gsheet_secret( + 'access_token', 'my-secret-token' +); +``` + + ## Secrets with `credential_chain` provider: For more advanced use-cases, one can define secrets with a `SERVER` (and `USER MAPPING`) on `duckdb` Foreign Data Wrapper: diff --git a/sql/pg_duckdb--1.0.0.sql b/sql/pg_duckdb--1.0.0.sql index 9ec4ca2e..e47be777 100644 --- a/sql/pg_duckdb--1.0.0.sql +++ b/sql/pg_duckdb--1.0.0.sql @@ -801,6 +801,52 @@ SET search_path = pg_catalog, pg_temp AS 'MODULE_PATHNAME', 'duckdb_only_function' LANGUAGE C; +-- read_xlsx function for single path +CREATE FUNCTION @extschema@.read_xlsx(path text, header BOOLEAN DEFAULT NULL, + sheet TEXT DEFAULT NULL, + all_varchar BOOLEAN DEFAULT FALSE, + ignore_errors BOOLEAN DEFAULT FALSE, + range TEXT DEFAULT NULL, + stop_at_empty BOOLEAN DEFAULT NULL, + empty_as_varchar BOOLEAN DEFAULT FALSE) +RETURNS SETOF duckdb.row +SET search_path = pg_catalog, pg_temp +AS 'MODULE_PATHNAME', 'duckdb_only_function' +LANGUAGE C; + +-- read_xlsx function for array of paths +CREATE FUNCTION @extschema@.read_xlsx(path text[], header BOOLEAN DEFAULT NULL, + sheet TEXT DEFAULT NULL, + all_varchar BOOLEAN DEFAULT FALSE, + ignore_errors BOOLEAN DEFAULT FALSE, + range TEXT DEFAULT NULL, + stop_at_empty BOOLEAN DEFAULT NULL, + empty_as_varchar BOOLEAN DEFAULT FALSE) +RETURNS SETOF duckdb.row +SET search_path = pg_catalog, pg_temp +AS 'MODULE_PATHNAME', 'duckdb_only_function' +LANGUAGE C; + +-- read_gsheet function for single path +CREATE FUNCTION @extschema@.read_gsheet(path text, header BOOLEAN DEFAULT NULL, + sheet TEXT DEFAULT NULL, + all_varchar BOOLEAN DEFAULT FALSE, + range TEXT DEFAULT NULL) +RETURNS SETOF duckdb.row +SET search_path = pg_catalog, pg_temp +AS 'MODULE_PATHNAME', 'duckdb_only_function' +LANGUAGE C; + +-- read_gsheet function for array of paths +CREATE FUNCTION @extschema@.read_gsheet(path text[], header BOOLEAN DEFAULT NULL, + sheet TEXT DEFAULT NULL, + all_varchar BOOLEAN DEFAULT FALSE, + range TEXT DEFAULT NULL) +RETURNS SETOF duckdb.row +SET search_path = pg_catalog, pg_temp +AS 'MODULE_PATHNAME', 'duckdb_only_function' +LANGUAGE C; + -- read_csv function for single path CREATE FUNCTION @extschema@.read_csv(path text, all_varchar BOOLEAN DEFAULT FALSE, allow_quoted_nulls BOOLEAN DEFAULT TRUE, @@ -1867,6 +1913,14 @@ RETURNS TEXT SET search_path = pg_catalog, pg_temp LANGUAGE C AS 'MODULE_PATHNAME', 'pgduckdb_create_azure_secret'; +CREATE FUNCTION duckdb.create_gsheet_secret( + provider text, + credential text +) +RETURNS text +LANGUAGE c +AS 'MODULE_PATHNAME', 'pgduckdb_create_gsheet_secret'; + CREATE FUNCTION duckdb.view(dbname text, schema text, view_name text, query text) RETURNS SETOF duckdb.row SET search_path = pg_catalog, pg_temp diff --git a/src/pgduckdb_options.cpp b/src/pgduckdb_options.cpp index edc02681..02ae65fa 100644 --- a/src/pgduckdb_options.cpp +++ b/src/pgduckdb_options.cpp @@ -232,6 +232,56 @@ DECLARE_PG_FUNCTION(pgduckdb_create_azure_secret) { PG_RETURN_TEXT_P(result); } +DECLARE_PG_FUNCTION(pgduckdb_create_gsheet_secret) { + auto provider_type = pgduckdb::pg::GetArgString(fcinfo, 0); + auto credential = pgduckdb::pg::GetArgString(fcinfo, 1); + auto lc_provider = duckdb::StringUtil::Lower(provider_type); + + if (lc_provider != "key_file" && lc_provider != "access_token") { + elog(ERROR, + "Invalid provider '%s'. For gsheet, supported providers are 'key_file' or 'access_token'.", + provider_type.c_str()); + } + + std::string secret_name_prefix = "gsheet_secret"; + SPI_connect(); + auto server_name = pgduckdb::FindServerName(secret_name_prefix.c_str()); + { + std::ostringstream create_server_query; + create_server_query << "CREATE SERVER " << server_name + << " TYPE 'gsheet' FOREIGN DATA WRAPPER duckdb"; + + auto ret = SPI_exec(create_server_query.str().c_str(), 0); + if (ret != SPI_OK_UTILITY) { + elog(ERROR, "Could not create 'gsheet' SERVER: %s", SPI_result_code_string(ret)); + } + } + { + std::ostringstream create_mapping_query; + create_mapping_query << "CREATE USER MAPPING FOR CURRENT_USER SERVER " + << server_name << " OPTIONS ("; + + create_mapping_query << "PROVIDER " << duckdb::KeywordHelper::WriteQuoted(lc_provider) << ", "; + + if (lc_provider == "key_file") { + create_mapping_query << "FILEPATH " << duckdb::KeywordHelper::WriteQuoted(credential); + } else { + create_mapping_query << "TOKEN " << duckdb::KeywordHelper::WriteQuoted(credential); + } + + create_mapping_query << ");"; + + auto ret = SPI_exec(create_mapping_query.str().c_str(), 0); + if (ret != SPI_OK_UTILITY) { + elog(ERROR, "Could not create 'gsheet' USER MAPPING: %s", SPI_result_code_string(ret)); + } + } + + SPI_finish(); + auto result = cstring_to_text(server_name.c_str()); + PG_RETURN_TEXT_P(result); +} + /* * We need these dummy cache functions so that people are able to load the * new pg_duckdb.so file with an old SQL version (where these functions still