gdrive
Query files in Google Drive directly with a gdrive:// filesystem
Maintainer(s): jrosskopf
Installing and Loading
INSTALL gdrive FROM community;LOAD gdrive;Example
-- Unattended: a service account. To WRITE you need a Shared Drive ---- service accounts have no personal Drive storage quota and get-- 403 storageQuotaExceeded anywhere else. Reading a shared folder is fine.---- DRIVE_ID is a SHARED DRIVE id; ROOT_FOLDER_ID is a FOLDER id. They are-- not interchangeable: passing a folder id as DRIVE_ID fails with-- "Shared drive not found". Use whichever matches what was shared.CREATE SECRET gdrive ( TYPE gdrive, PROVIDER service_account, KEY_FILE '/etc/creds/sa.json', DRIVE_ID '0ABcDeFgHiJkLmNoPQ', -- or ROOT_FOLDER_ID '1a2b3c...' DRIVE_SCOPE 'https://www.googleapis.com/auth/drive.readonly');
-- Query a file by path. Drive has no path addressing, so each segment-- costs a lookup; results are cached per secret.SELECT count(*) FROM 'gdrive://Finance/2026/actuals.parquet';
-- Address a file by id instead: zero resolution calls. Prefer this in-- generated or stored queries.SELECT * FROM read_csv('gdrive://id:1a2b3cDeFgHiJkLmNoPQrStUvWxYz');
-- Glob a folder.SELECT * FROM read_parquet('gdrive://exports/parts/*.parquet');
-- A native Google Sheet is queryable with no manual export: it has no-- byte stream, so it is served through Drive's CSV export.SELECT * FROM read_csv('gdrive://Finance/Budget');
-- Write a whole file. Writes are sequential; Drive cannot write at a-- byte offset, so positional writes raise an error rather than corrupt.COPY (SELECT * FROM t) TO 'gdrive://reports/out.parquet' (FORMAT parquet);
-- Inspect API calls and cache hits for the session.SELECT * FROM gdrive_stats();About gdrive
Registers a gdrive:// filesystem, so any DuckDB path expression can
address a file in Google Drive with no download or copy step. Because
DuckDB dispatches file access through its virtual filesystem, everything
layered on it inherits the scheme: read_parquet, read_csv, COPY,
glob, and table formats such as DuckLake — attaching a DuckLake whose
DATA_PATH is on Drive works and is tested live.
ATTACH of a DuckDB database file on gdrive:// is NOT supported.
DuckDB opens database files through a different path that does not consult
the virtual filesystem, so the attempt fails immediately rather than
half-working.
This is Google Drive, not Google Cloud Storage — a different product
with a different API and access model. For gs:// buckets use the gcs
extension.
Reads are ranged, so a Parquet scan fetches footers and column chunks rather than whole files. Path resolution is cached per secret and per drive, because Drive has no path addressing and each segment otherwise costs an API call.
Two behaviours are deliberately strict. Drive permits two files with the same name in one folder, so a path is not a unique identifier: rather than pick one and make results depend on Drive’s internal ordering, ambiguity is an error naming both file ids. And Drive cannot write at a byte offset, so positional writes raise an error instead of silently corrupting a file.
Drive is slower than object storage by a wide margin, and enforces per-user API quotas that object storage does not. A ranged read costs ~1.3 s regardless of how few bytes it requests, against ~1 ms for S3/GCS. Use this when Drive is the system of record for data you want to query and you would rather not maintain a second copy — not as a substitute for object storage under a hot workload. On a workstation, Google Drive for desktop plus ordinary local paths is simpler and faster.
Added Functions
| function_name | function_type | description | comment | examples |
|---|---|---|---|---|
| file_size | scalar | Byte length of the file at path, or NULL if it does not exist. Reads only metadata for ordinary files – unlike read_blob, which downloads the body. NOTE: a native Google Doc or Sheet has no stored byte size, so its size is the length of the EXPORT (see gdrive_docs_export_mime) and obtaining it downloads that export. Unlike read_blob(), which reports the same number but downloads the whole body to do it. |
NULL | [SELECT file_size(‘gdrive://data/part.parquet’)] |
| gdrive_reset_stats | table | Zero the process-wide Drive API call counters reported by gdrive_stats(). Use it to measure exactly what ONE operation costs: reset, run the query, then read gdrive_stats(). Affects the whole process, so it will disturb a concurrent measurement in another connection. | NULL | [CALL gdrive_reset_stats()] |
| gdrive_stats | table | Drive API call counters, one row per metric: files_get, files_list, files_media, files_export, files_create, files_update, files_delete (calls by kind), cache_hits/cache_misses (the path-resolution cache that mitigates R-1 amplification), retries (retried HTTP attempts across all kinds), and total (sum of the files_* kind counters), and path_cache_entries (a GAUGE: the live size of the path->id cache, bounded by gdrive_path_cache_entries). Process-wide, not reset between queries – call it before and after an operation and diff the two snapshots to measure that operation’s amplification. | NULL | [SELECT * FROM gdrive_stats()] |
| gdrive_version | scalar | NULL | NULL | |
| move_file | scalar | Rename/move source to target, returning true on success and raising on failure. Dispatches on the path’s scheme through DuckDB’s virtual filesystem. Both paths must live on the SAME filesystem – this is a rename, not a copy, and it does not move bytes between schemes. Its main use is publishing a fully-written temporary file under its final name. |
NULL | [SELECT move_file(‘gdrive://staging/part.tmp’, ‘gdrive://data/part.parquet’)] |
| remove_file | scalar | Delete the file at path, returning true if it existed and was removed and false if it did not exist. Dispatches on the path’s scheme through DuckDB’s virtual filesystem, so it works for gdrive://, s3://, gs:// and local paths alike. For a gdrive:// path the file is moved to the trash unless gdrive_permanent_delete is set. Errors other than not-found are raised, not returned as false. |
NULL | [SELECT remove_file(‘gdrive://reports/old.parquet’)] |
| write_blob | scalar | Write content to path, replacing any existing file, and return the number of bytes written. The inverse of read_blob(): together they give SQL a byte-exact round trip for any filesystem DuckDB can reach, including gdrive://, with no CSV/Parquet encoding in between. Accepts arbitrary binary content – a BLOB, not a VARCHAR – so it is safe for images and PDFs as well as text. |
NULL | [SELECT write_blob(‘gdrive://notes/readme.md’, ‘# Title’::BLOB)] |
Overloaded Functions
This extension does not add any function overloads.
Added Types
This extension does not add any types.
Added Settings
| name | description | input_type | scope | aliases |
|---|---|---|---|---|
| datazoo_banner | Show the DataZoo feedback banner when an extension is loaded in an interactive terminal (at most once a day per extension). | BOOLEAN | GLOBAL | [] |
| gdrive_adc_file | Path to an Application Default Credentials JSON file, overriding normal discovery for PROVIDER credential_chain. Empty (default) resolves GOOGLE_APPLICATION_CREDENTIALS, then CLOUDSDK_CONFIG, then the well-known gcloud location. | VARCHAR | GLOBAL | [] |
| gdrive_block_cache_bytes | Total memory the shared block cache may hold (default 256 MiB). Least-recently-used blocks are evicted above this. Shared by all files and all threads. | UBIGINT | GLOBAL | [] |
| gdrive_block_size_bytes | Block size for cached reads (default 16 MiB, 0 to disable and read exact ranges). Drive charges roughly the same for a 1 KB and a 16 MB request, so larger blocks trade bandwidth for far fewer round trips. | UBIGINT | GLOBAL | [] |
| gdrive_docs_export_mime | MIME type to export application/vnd.google-apps.document files as: ‘text/plain’ (default) or ‘text/markdown’. Sheets always export to text/csv. | VARCHAR | GLOBAL | [] |
| gdrive_immutable_prefixes | Comma-separated gdrive:// path prefixes whose files are never overwritten in place (e.g. a DuckLake DATA_PATH). Skips the per-open metadata refresh for them. Matching is on whole path segments. Empty (the default) disables it. Declaring a prefix whose files ARE rewritten in place causes stale reads that cannot be detected. | VARCHAR | GLOBAL | [] |
| gdrive_path_cache_entries | Maximum path->file-id mappings to cache (default 4096, 0 for unbounded). Least-recently-used entries are dropped above this; a dropped mapping just costs one files.list per segment to rebuild. | UBIGINT | GLOBAL | [] |
| gdrive_permanent_delete | RemoveFile permanently deletes instead of moving to trash (default: false, trash). | BOOLEAN | GLOBAL | [] |