Exporting Query Results
PuppyGraph supports exporting query results to cloud storage services.
A query names its destination by referring to an export location that an administrator has registered. Each location holds a storage type, a base path, and the credentials needed to reach it, and a query supplies the location name plus a directory beneath that path. A catalog already registered in the graph schema can serve as a destination as well.
Supported storage types and file formats
Storage Types
- Amazon S3
- Azure Data Lake Storage (Gen2)
- Google Cloud Storage
- MinIO
- HDFS
File Formats
- CSV files
- Parquet files
- Parquet Iceberg tables, through a schema catalog only
- Parquet Hive tables, through a schema catalog only
Syntax
A CSV export with the default transfer settings needs no other options, so the PROPERTIES block is
optional in OpenCypher.
Parameters
| Key | Description | Default Value |
|---|---|---|
exportTo |
Export target: <location>:<directory> for a registered export location, or a path URI or <database>.<table> name when catalog is set |
- |
fileType |
Target file format (csv, parquet, or table) |
csv |
catalog |
Catalog name defined in the schema, to export with that catalog's own storage configuration | - |
transferSize |
Number of rows per transfer batch | 10000 |
timeout |
Export timeout in seconds | 43200 |
Registering an export location
Registering, changing, and deleting export locations requires the CATALOG:write permission, which
only the Admin role holds. Listing them requires CATALOG:read, which every role holds, so any
query author can discover the available names, and credentials are masked in the listing. Every user
who can run a query can export to every registered location. See
Role-Based Access Control.
Manage locations from Exports in the left sidebar of the Web UI, or through any of the interfaces below. A change is pushed to every running query engine immediately and resent periodically, so a restarted engine picks the registered locations up again on its own.
Location fields
name: how queries refer to the location. Letters, digits,_, and-, starting with a letter or_.type: one ofMINIO,S3,GCS,AZURE_DLS2, orHDFS.path: the base URI that every export to this location is written under. Its scheme must match the type:s3://ors3a://forMINIOandS3,gs://forGCS,abfs://orabfss://forAZURE_DLS2, andhdfs://forHDFS. A trailing/is added if missing.- The remaining fields are the storage settings for that type, listed in Location settings per storage type.
REST API
# register
curl -u <user>:<password> -H 'content-type: application/json' \
-X POST http://localhost:8081/ui-api/exportLocation -d '{
"name": "minio_test",
"type": "MINIO",
"path": "s3://test/",
"endpoint": "http://minio:9000",
"identifier": "admin",
"credential": "password"
}'
# list, with credentials masked as ******
curl -u <user>:<password> http://localhost:8081/ui-api/exportLocation
# change: send the whole location again. A masked credential keeps the stored value.
curl -u <user>:<password> -H 'content-type: application/json' \
-X PUT http://localhost:8081/ui-api/exportLocation -d '{ ... }'
# delete
curl -u <user>:<password> -X DELETE 'http://localhost:8081/ui-api/exportLocation?name=minio_test'
From a query session
CALL puppy.schema.addExportLocation($location) YIELD success, error_message
CALL puppy.schema.updateExportLocation($location) YIELD success, error_message
CALL puppy.schema.listExportLocations() YIELD name
CALL puppy.schema.removeExportLocation('minio_test') YIELD success, error_message
$location is the location JSON, bound as a query parameter.
graph.getTopology2().addExportLocation('{"name": "minio_test", "type": "MINIO", "path": "s3://test/", "endpoint": "http://minio:9000", "identifier": "admin", "credential": "password"}')
graph.getTopology2().updateExportLocation('{"name": "minio_test", "type": "MINIO", "path": "s3://test/", "endpoint": "http://minio:9000", "identifier": "admin", "credential": "password"}')
graph.getTopology2().listExportLocations()
graph.getTopology2().removeExportLocation('{"name": "minio_test"}')
Location settings per storage type
Amazon S3
Before exporting to Amazon S3, ensure that:
- The registered credentials have been granted write permissions to the target S3 bucket
- The IAM policy includes proper authorization for
s3:PutObjectactions
{
"name": "s3_exports",
"type": "S3",
"path": "s3://your_bucket/exports/",
"region": "us-east-1",
"identifier": "<aws_access_key_id>",
"credential": "<aws_secret_access_key>"
}
region: AWS region where the target S3 bucket is locatedidentifier: AWS access key ID for S3 authenticationcredential: Corresponding secret access key for AWS authentication
MinIO
Before registering the location, ensure the credentials have write access to the target bucket.
{
"name": "minio_test",
"type": "MINIO",
"path": "s3://test/",
"endpoint": "http://minio:9000",
"identifier": "<user>",
"credential": "<password>"
}
endpoint: Endpoint URL of the MinIO service. TLS is used when it starts withhttps://identifier: Authentication username for MinIOcredential: Authentication password for MinIO
Google Cloud Storage
Before registering the location, ensure the credentials have write access to the target bucket.
To authenticate with a JSON key file, mount the key file during container creation and set the
GOOGLE_APPLICATION_CREDENTIALS environment variable to the mounted path inside the container. See
Authentication for PuppyGraph to access Google Cloud resources.
The location then needs no credential fields:
To use the service account attached to the Compute Engine VM instance PuppyGraph runs on, set
useComputeEngineService. The instance must be associated with a service account whose access scope
covers storage operations. See
service accounts.
{ "name": "gcs_exports", "type": "GCS", "path": "gs://your_bucket/exports/", "useComputeEngineService": true }
To register a service account key directly:
{
"name": "gcs_exports",
"type": "GCS",
"path": "gs://your_bucket/exports/",
"serviceAccountEmail": "<email>",
"identifier": "<key_id>",
"credential": "<secret_key>"
}
serviceAccountEmail: Service account email address associated with your Google Cloud projectidentifier: Private key ID of the service accountcredential: Private key of the service accountimpersonationServiceAccount: Optional service account to impersonate
Azure Data Lake Storage Gen2
Before registering the location, ensure the credentials have write access to the target storage
location. The path has the form
abfss://<container-name>@<storage-account-name>.dfs.core.windows.net/<path>/.
Using a storage account access key:
{
"name": "adls_exports",
"type": "AZURE_DLS2",
"path": "abfss://<container-name>@<storage-account-name>.dfs.core.windows.net/exports/",
"identifier": "<storage-account-name>",
"credential": "<storage-account-access-key>"
}
Using a managed identity:
{
"name": "adls_exports",
"type": "AZURE_DLS2",
"path": "abfss://<container-name>@<storage-account-name>.dfs.core.windows.net/exports/",
"useManagedIdentity": true,
"tenantId": "<tenant-id-of-identity>",
"clientId": "<client-id-of-identity>"
}
identifier: Name of your Azure Storage Accountcredential: Access key for the storage accounttenantId: Tenant ID of the managed identityclientId: Client ID (application ID) of the managed identity
A workload identity adds useWorkloadIdentity and tokenFile. A service principal with a client
secret uses clientId, clientSecret, and endpoint, the OAuth2 token URL.
HDFS
Before registering the location, ensure you have write access to the target path.
Add identifier and credential for simple authentication.
Exporting to a registered location
In a query, the EXPORT TO target in OpenCypher and the exportTo option in Gremlin take the form
<location>:<directory>. The name before the first colon identifies the registered location, and
everything after it is a directory under that location's path. With the minio_test location above,
minio_test:reports/2026 writes to s3://test/reports/2026/.
The directory accepts letters and digits in any script, along with ., _, -, =, :, and /,
so a timestamped directory such as minio_test:dt=2026-09-05T00:00:00Z/ works. Use a single colon
after the location name, not ://, and keep the directory relative so that it stays under the
location's path. An empty directory, as in minio_test:, exports directly under that path.
For Parquet, add PROPERTIES { fileType: 'parquet' } after the target in OpenCypher, or
.with("fileType", "parquet") in Gremlin.
Export using schema catalog configuration
When the export destination storage matches the schema's catalog configuration, you can reuse
existing storage settings. Before saving data to this location, ensure you possess appropriate write
access privileges. With catalog set, the target is interpreted as a catalog destination rather
than a location reference.
Export to cloud storage
<target_path>: Storage path URI matching catalog scheme (e.g.,s3://my_bucket/subfolder)<catalog name>: Name of the catalog defined in the graph schema
Bounding catalog export paths
A catalog export uses the catalog's credentials while the query chooses the path, and for hdfs://,
abfss://, and gs:// URIs the path includes the host. An administrator can bound this with the
runtime property feature.engine.exportCatalogAllowedPathPrefixes, a comma-separated list of URI
prefixes the export path must start with. Prefixes are compared exactly and case-sensitively, after
. and .. segments are refused. The default * allows any path, and every query engine logs it at
WARN on startup and whenever it takes effect, so an unconfigured deployment is visible in its logs.
curl -u <user>:<password> -X PUT http://localhost:8081/ui-api/features \
-H 'Content-Type: application/json' \
-d '{"properties": {"feature.engine.exportCatalogAllowedPathPrefixes": "s3://warehouse/exports/,gs://analytics/exports/"}}'
Exporting as a table is not affected, because its target is a <database>.<table> name in the
catalog rather than a path.
Export as Iceberg/Hive table
It is also possible to store query results as an Iceberg or Hive table. This feature is currently experimental.
To store results as an Iceberg table:
- Configure the catalog type in the graph schema as Iceberg.
- Ensure the Iceberg catalog service aligns with the schema configuration.
- Confirm you have
CREATE TABLEprivileges on the target Iceberg database (schema). - Check that you have write permissions for the designated storage path.
To store results as a Hive table:
- Configure the catalog type in the graph schema as Hive (See Connecting to Hive).
- If you are using a kerberized Hive cluster, ensure you have configured PuppyGraph with the Kerberos settings (See Querying Kerberized Hive Data as a Graph).
- Ensure the Hive catalog service aligns with the schema configuration.
- Confirm you have
CREATE TABLEprivileges on the target Hive database. - Check that you have write permissions for the designated storage path.
Query results will be stored as Parquet files in the new table. A table export requires catalog;
it cannot be written to an export location.
<target_database>.<target_table>: The database and table to save to, database name must be provided. e.g.mydatabase.result_table<catalog name>: Name of the catalog defined in the graph schema. It must be of Iceberg or Hive type.fileTypeparameter: Must be set totable