CDS pluginCDS plugintypeInstalls into a CAP project and extends the runtime. CAP loads it through cds.requires or the plugin hook.See every one →What this type is →independently maintainedindependently maintainedoriginPublished by one person or a small company outside SAP. Says nothing about quality; the readings say that.See every one →

cap-snowflake

A CAP database adapter for Snowflake with OData V4 support, which the maintainer marks as early development, so its contract is expected to move.

Facts

Latestv1.1.0ReleasedLast pushLicenceApache-2.0

Where it sits

Lives in
Provides
Database connectorsCAPDatabase connectorscapabilityConnecting an application’s data model and queries to a database, including the differences in supported queries, types and schema management between database implementations. Changing an existing schema belongs to [schema evolution](capability:schema-evolution).
Flavors
Node.jsfrom its runtime
Requires
@sap/cds≥ 9.0.0@sap/cdsartifactThe peer range, as published.

Who is behind it

Sören LeibachSören LeibachPersonMaintains this
In the atlas: maintains 6, contributes to 5, wrote 1
@sleibach
maintainer

Before choosing this

Conditions

LicenceSAP Developer License Agreement 3.2 CAP§ 1, 2, 3A cds-plugin is used with SAP CAP materials, or an artifact is explicitly identified as a CAP plugin or application.
  • § 1 License. Productive customer applications are limited to SAP BTP or another SAP-licensed platform, and mass extraction to a non-SAP product is restricted except where solely required for SAP interoperability.
  • § 2 Intellectual Property. The agreement retains SAP's rights in the covered materials and limits making APIs, tools or software available to third parties, while preserving ownership of customer applications subject to SAP's underlying rights.
  • § 3 Artificial Intelligence Training. The agreement prohibits use of the covered materials and customer applications for training artificial-intelligence models or systems.
SAP Developer License Agreement 3.2 CAP →

Also, from Database connectors§ 1: The connector itself does not decide the licence boundary. Section 1 makes the destination and purpose of extraction relevant, including the scoped exception for interoperability with an SAP product.

PracticalFunctional boundary

The maintainer marks this early development; expect the contract to move.

What it declares

Every artifact

Type
CDS plugin
Origin
Independently maintained
Runtime
Node.js

As a CDS plugin

Declared for
>=9.0.0
Distributed through
npm

Connections1

cap-snowflake connectionscap-snowflakePROVIDES→ provides1provides authoredDatabase connectors

Install

npm add cap-snowflake

Derived from the package name. No recipe recorded; the README has the configuration.

README

quoted from sleibach/cap-snowflake under Apache-2.0, read
sleibach/cap-snowflake/README.mdOn GitHub

cap-snowflake

SAP CAP database adapter for Snowflake — OData V4 support for the SAP Cloud Application Programming / CDS Model.

License Node.js Version npm version

Early Development Notice

This adapter is in active development and has not yet reached a stable release. Public APIs, configuration options, and internal behaviour may change between versions without prior notice. It is not recommended for production use without thorough evaluation in your specific environment. Feedback, bug reports, and contributions are welcome via GitHub Issues.


Table of Contents

  1. Overview
  2. Architect's Note — When to Use Snowflake vs. HANA
  3. Prerequisites
  4. Installation
  5. Quick Start
  6. Authentication
  7. Configuration Reference
  8. Schema Deployment
  9. OData V4 Feature Coverage
  10. Advanced Features
  11. Snowflake-Native Features
  12. CDS Type Mappings
  13. Identifier Handling
  14. Limitations
  15. Troubleshooting
  16. Example Projects
  17. Development
  18. License
  19. Support

Overview

cap-snowflake implements the cds.DatabaseService interface from @cap-js/db-service, allowing Snowflake to serve as the persistence layer for SAP CAP applications. It translates CAP Query Notation (CQN) to Snowflake SQL and provides full OData V4 compatibility for CAP services.

Connectivity Modes
ModeProtocolAuthenticationRecommended For
SQL API (default)HTTPS RESTJWT key-pairSAP BTP, Cloud Foundry, serverless
SDKNative Snowflake driverUsername/passwordOn-premise, local development

Architect's Note — When to Use Snowflake vs. HANA

While this adapter makes it technically possible to use Snowflake as the primary transactional database for a CAP application, that does not mean it is always the right choice. Snowflake is a cloud data warehouse optimised for large-scale analytical workloads — massively parallel scans, complex aggregations, and multi-terabyte joins across historical data. HANA, by contrast, is purpose-built for high-frequency OLTP: row-level locking, sub-millisecond point reads, and tight transactional guarantees that Fiori-driven business applications rely on.

Using Snowflake as a transactional store means working against the grain of how the platform is designed and priced. Snowflake's per-second compute billing and connection latency profile favour batch and analytical queries, not the many small, latency-sensitive reads and writes that a transactional UI generates. Draft handling, deep insertions, and concurrent user sessions are functional — but they stress exactly the characteristics where Snowflake offers the least advantage.

The recommended architecture for most SAP landscapes is the one shown in Pattern B: HANA handles all transactional data; Snowflake serves as a downstream analytical store or data mart — fed by replication or ETL — where complex reporting, ML scoring, and cross-system aggregations run without impacting the transactional tier.

The one pragmatic exception is cost consolidation: if Snowflake is already the only database available in a given environment and the workload is genuinely light, using it as the sole persistence layer can make sense. But that is an infrastructure constraint driving an architectural decision, not an architectural best practice. Choose the right tool for the job, not just the available one.


Prerequisites

  • Node.js 20 or later
  • SAP CAP (@sap/cds) 7.0 or later
  • A Snowflake account with a dedicated user, role, warehouse, database, and schema

Installation

npm install cap-snowflake

Or pin to an exact version (recommended while the API is still under active development — see the notice at the top of this README):

npm install cap-snowflake@1.0.0

Source: github.com/sleibach/cap-snowflake npm: npmjs.com/package/cap-snowflake


Quick Start

There are two typical usage patterns: Snowflake as the primary (only) database, or as a named secondary database alongside a primary DB such as HANA.


Pattern A — Primary database

Use this when Snowflake is the sole persistence layer for the application.

package.json:

{
  "dependencies": {
    "cap-snowflake": "^1.0.0"
  },
  "cds": {
    "requires": {
      "db": {
        "kind": "snowflake",
        "impl": "cap-snowflake"
      }
    }
  }
}

The plugin self-registers via the cds-plugin.js convention, so the impl line is only required if you want to be explicit (and if so, prefer the bare module name "cap-snowflake" over a path into node_modules/).

Credentials — ~/.cdsrc.json (local dev) or a bound user-provided service on BTP/CF:

{
  "requires": {
    "db": {
      "credentials": {
        "account": "myorg-myaccount",
        "host": "myorg-myaccount.snowflakecomputing.com",
        "user": "CAP_USER",
        "role": "CAP_ROLE",
        "warehouse": "CAP_WH",
        "database": "CAP_DB",
        "schema": "APP",
        "auth": "jwt",
        "jwt": {
          "privateKey": "env:SNOWFLAKE_PRIVATE_KEY",
          "privateKeyPassphrase": "env:SNOWFLAKE_PASSPHRASE"
        }
      }
    }
  }
}
cds deploy --to snowflake    # creates tables in Snowflake
cds serve                    # starts the CAP OData service

Pattern B — Named secondary database (alongside HANA or SQLite)

The more common real-world scenario is using Snowflake as an analytics / reporting data mart while a primary DB (HANA, SQLite) handles transactional data. Register the adapter under a custom service name instead of db.

package.json:

{
  "dependencies": {
    "@cap-js/hana": "^2",
    "cap-snowflake": "^1.0.0"
  },
  "cds": {
    "requires": {
      "db": { "kind": "hana" },
      "snowflake": {
        "kind": "snowflake",
        "impl": "cap-snowflake"
      }
    }
  }
}

Credentials — provide separately for each service in ~/.cdsrc-private.json:

{
  "cds": {
    "requires": {
      "snowflake": {
        "credentials": {
          "account": "myorg-myaccount",
          "host": "myorg-myaccount.eu-central-1.snowflakecomputing.com",
          "user": "CAP_SVC_USER",
          "role": "CAP_SVC_ROLE",
          "warehouse": "CAP_WH",
          "database": "CAP_DB",
          "schema": "DATA_MART",
          "auth": "jwt",
          "jwt": {
            "privateKey": "env:SNOWFLAKE_PRIVATE_KEY"
          }
        }
      }
    }
  }
}

Service handler — connect to the named service and delegate reads:

// srv/MyAnalyticsService.js
const cds = require('@sap/cds');

module.exports = async function () {
  const snowflake = await cds.connect.to('snowflake');

  this.on('READ', 'MaterialValuation', async (req) => {
    return snowflake.run(req.query);
  });
};

CDS model — mark the entity as non-persistent in the primary DB and map it to the Snowflake table name:

// srv/MyAnalyticsService.cds
service MyAnalyticsService {
  @cds.persistence.skip
  @cds.persistence.name: 'MATERIAL_VALUATION'
  entity MaterialValuation {
    key material_id : String;
    name            : String;
    stock           : Decimal;
    coverage_days   : Decimal;
  };
}

@cds.persistence.skip prevents CAP from trying to deploy this entity to the primary DB. @cds.persistence.name maps it to the physical Snowflake table name.

Cloud Foundry / BTP: Supply credentials at runtime via a user-provided service instance bound to the application. Do not hard-code credentials in project files or environment variables stored in the repository.


Authentication

RSA key-pair authentication is the recommended approach for all cloud deployments. Tokens are short-lived and automatically refreshed.

Generate a key pair:

# Generate RSA private key (PKCS#8, unencrypted)
openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -out snowflake_key.p8 -nocrypt

# Extract public key
openssl rsa -in snowflake_key.p8 -pubout -out snowflake_key.pub

# Register the public key with your Snowflake user
ALTER USER CAP_USER SET RSA_PUBLIC_KEY='<content of snowflake_key.pub without headers>';

Configuration:

{
  "credentials": {
    "auth": "jwt",
    "jwt": {
      "privateKey": "env:SNOWFLAKE_PRIVATE_KEY",
      "privateKeyPassphrase": "env:SNOWFLAKE_PASSPHRASE",
      "algorithm": "RS256",
      "expiresIn": 3600
    }
  }
}

The env: prefix instructs the adapter to read the value from the named environment variable at runtime.

SDK / Password

For local development or environments where key-pair authentication is not available:

{
  "credentials": {
    "auth": "sdk",
    "password": "env:SNOWFLAKE_PASSWORD"
  }
}

Configuration Reference

PropertyRequiredDefaultDescription
account✅—Snowflake account identifier (e.g., myorg-myaccount)
host{account}.snowflakecomputing.comSnowflake endpoint
user✅—Snowflake username
role—Role to assume for all statements
warehouse—Virtual warehouse for compute
database—Default database
schema—Default schema
auth✅—jwt or sdk
jwt.privateKey(jwt)—PEM private key or env:VAR_NAME reference
jwt.privateKeyPassphrase—Passphrase for encrypted key
jwt.expiresIn3600Token lifetime in seconds
password(sdk)—Password or env:VAR_NAME reference
timeout60Query timeout in seconds
serviceName—Optional: override service name in requests

Schema Deployment

The adapter provides model-driven schema deployment via cds deploy:

cds deploy --to snowflake

What gets created:

ArtifactDescription
Base tablesOne table per persistent CDS entity
Localized .texts tablesFor entities with localized elements
Localized viewslocalized_<Entity> views with COALESCE locale fallback
Draft tables<Entity>.drafts tables for @odata.draft.enabled entities
Temporal views<Entity>_current views for temporal entities
External tablesCREATE EXTERNAL TABLE for entities with @Snowflake.external

Schema evolution:

Re-running cds deploy on an existing database is idempotent — tables already present are preserved. New columns are added with ALTER TABLE ... ADD COLUMN. The adapter follows an add-only policy; column removal requires explicit migration SQL.

Post-DDL Snowflake annotation pass:

After each CREATE TABLE, the adapter automatically runs additional ALTER TABLE statements to apply Snowflake-specific annotations declared in the CDS model (clustering keys, data retention, search optimization, masking policies, row access policies, and tags). See Snowflake-Native Features for details.


OData V4 Feature Coverage

Query Capabilities
FeatureOData SyntaxStatus
Column projection$select=field1,field2✅
Equality / comparison filters$filter=price gt 10✅
String functions$filter=contains(title,'cap')✅
String concatenation$filter=concat(firstName,' ',lastName) eq 'John Doe'✅
String position$filter=indexof(title,'cap') ge 0✅
String trimming$filter=trim(name) eq 'Alice'✅
Case functions$filter=tolower(title) eq 'cap'✅
Math functions$filter=round(price) eq 20✅
Date/time functions$filter=year(createdAt) eq 2024✅
Null comparisons$filter=field eq null✅
Boolean logic$filter=... and/or/not ...✅
Navigation property filter$filter=author/name eq 'Doe'✅
Lambda operators$filter=books/any(b:b/price gt 30)✅
Free-text search$search=keyword✅
Sorting$orderby=title asc,price desc✅
Pagination$top=10&$skip=20✅
Total row count$count=true✅
Inline count$inlinecount=allpages✅
Aggregation$apply=groupby((field),aggregate(...))✅
Aggregation filter$apply=filter(price gt 10)/aggregate(...)✅
$expand Support
FeatureStatus
To-one association (LEFT JOIN)✅
To-many association (ARRAY_AGG)✅
Nested $expand (multi-level)✅
$expand with $select✅
$expand with $filter✅
$expand with $orderby✅
$expand with $top/$skip✅
$expand with $count✅
Navigation property read✅
Data Modification
FeatureStatus
Single entity INSERT✅
UUID auto-generation (@cds.on.insert: $uuid)✅
Deep insert (compositions)✅
Managed fields on insert (createdAt, createdBy)✅
Single field PATCH✅
Deep UPDATE (nested compositions)✅
Null field update via PATCH✅
Managed fields on update (modifiedAt, modifiedBy)✅
DELETE single entity✅
CASCADE DELETE (compositions)✅
UPSERT (MERGE)✅
404 on PATCH/DELETE of non-existent entity✅
OData Draft (Fiori Elements)
FlowStatus
Create draft (POST with empty body)✅
Edit existing active entity (draftEdit)✅
Patch draft fields (PATCH IsActiveEntity=false)✅
Read draft with $expand=DraftAdministrativeData✅
Activate draft (draftActivate)✅
Discard draft (DELETE IsActiveEntity=false)✅
Draft list filter (IsActiveEntity eq true or SiblingEntity/...)✅
Deep draft with composed entities✅

Advanced Features

Localization

The adapter supports CAP's localization pattern for multi-language content:

entity Books {
  key ID    : UUID;
  title     : localized String;
  abstract  : localized LargeString;
}

At deploy time the adapter creates:

  • BOOKS_TEXTS — stores translations per (ID, locale) composite key
  • LOCALIZED_BOOKS — view with COALESCE(texts.title, base.title) fallback to the default locale

At query time, the adapter resolves the locale from Accept-Language (via cds.context.locale) and injects a runtime JOIN on the texts table with the current locale value.

Temporal Data

Application-time period tables (time slices) are supported via the temporal aspect:

using { temporal } from '@sap/cds/common';

entity Assignments : temporal {
  key ID    : UUID;
  role      : String;
  // Inherits: validFrom : DateTime @cds.valid.from
  //           validTo   : DateTime @cds.valid.to
}

The adapter generates a _CURRENT view that filters to the active slice using CURRENT_TIMESTAMP BETWEEN validFrom AND validTo. Point-in-time reads are supported via the sap-valid-at request header.

Compositions (Deep CRUD)

Entities linked by Composition of many participate in deep operations automatically:

entity Catalogs : cuid, managed {
  name  : String(100);
  items : Composition of many CatalogItems on items.catalog = $self;
}

entity CatalogItems : cuid, managed {
  catalog : Association to Catalogs;
  title   : String(100);
  price   : Decimal(10,2);
}
  • Deep INSERT: POST /Catalogs with { name: '...', items: [...] } creates parent and all children in a single transaction.
  • Deep UPDATE: PATCH /Catalogs(id) with an items array updates child records.
  • Cascade DELETE: DELETE /Catalogs(id) automatically deletes all CatalogItems children before removing the parent.
Star Schema and Analytics

The adapter fully supports Snowflake star schema patterns for analytical workloads. A fact table with FK associations to dimension tables can be queried with $apply groupby expressions that navigate through association paths:

entity SalesFacts : cuid, managed {
  book    : Association to Books;
  region  : String(50);
  channel : String(50);
  amount  : Decimal(10,2);
  units   : Integer;
}

OData $apply examples:

# Simple measure aggregation
GET /SalesFacts?$apply=aggregate(units with sum as totalUnits)

# Groupby a local dimension column
GET /SalesFacts?$apply=groupby((channel),aggregate(amount with sum as totalAmount))

# Groupby a navigation path (requires JOIN into dimension table)
GET /SalesFacts?$apply=groupby((book/title),aggregate(units with sum as totalUnits))

Navigation-path groupby expressions (e.g. book/title) are resolved by cqn4sql into proper JOINs before reaching the adapter, following the same pattern as HANA and SQLite adapters.

Schema introspection annotations:

When reverse-engineering a Snowflake schema, the introspector automatically annotates entities with @Analytics.dataCategory and measure columns with @Aggregation.default based on structural heuristics (association count, column types):

@Analytics.dataCategory: #FACT
entity SalesFacts { ... }

@Analytics.dataCategory: #DIMENSION
entity Books { ... }
Schema Introspection

Import existing Snowflake tables as CDS entity definitions:

npx cap-snowflake-import --schema=MY_SCHEMA --output=db/schema.cds

The tool introspects tables and views, converts Snowflake types to CDS types, derives associations from naming conventions, and generates ready-to-use .cds files. Fact and dimension tables are annotated automatically based on association structure.


Snowflake-Native Features

The @Snowflake.* annotation namespace lets you express Snowflake-specific capabilities directly in the CDS model without leaving the model layer. The adapter's deployment pass translates these annotations into the appropriate DDL statements after the base CREATE TABLE.

The annotation vocabulary is defined in src/annotations/snowflake.cds. No additional imports are required — annotations are read from the compiled CSN at deploy time.

Use the standard CDS Vector(n) type to store machine-learning embeddings. The dimension count is specified as the type parameter — no extra annotation required:

entity EmbeddedDocs {
  key ID      : UUID;
  content     : String;
  embedding   : Vector(1536);
}

DDL generated:

EMBEDDING VECTOR(FLOAT, 1536)

The @Snowflake.vector annotation is optional and only needed to override the default similarity function used by vectorSearch:

entity EmbeddedDocs {
  key ID      : UUID;
  content     : String;
  embedding   : Vector(768) @Snowflake.vector: { similarity: 'DOT_PRODUCT' };
}

Supported similarity functions:

similarity valueSnowflake function
COSINE (default)VECTOR_COSINE_SIMILARITY
DOT_PRODUCTVECTOR_INNER_PRODUCT
EUCLIDEANVECTOR_L2_DISTANCE

vectorSearch action:

The adapter exposes a vectorSearch action for ranked similarity queries:

const results = await db.run('vectorSearch', {
  entity:       'my.EmbeddedDocs',
  queryVector:  [0.1, 0.2, ...],   // float array matching column dimensions
  topK:         10,                 // default: 10
  similarityFn: 'COSINE',          // default: 'COSINE'
});
// Returns rows ordered by similarity score (_score field added)

Generated SQL (COSINE example):

SELECT *, VECTOR_COSINE_SIMILARITY(EMBEDDING, [...]::VECTOR(FLOAT, 1536)) AS _score
FROM MY_DB.MY_SCHEMA.MY_EMBEDDEDDOCS
ORDER BY _score DESC
LIMIT 10
Clustering Keys

Define a Snowflake micro-partition clustering key on a table. This improves pruning performance for large tables that are frequently filtered on specific columns.

@Snowflake.clustering: ['createdAt', 'region']
entity Events : cuid, managed {
  region    : String(50);
  eventType : String(100);
}

DDL generated (post-CREATE):

ALTER TABLE EVENTS CLUSTER BY (CREATEDAT, REGION)
Time Travel

Query data as it existed at a specific point in the past using Snowflake's Time Travel feature. Include the sap-snowflake-at HTTP header with an ISO 8601 timestamp in any OData GET request:

GET /odata/v4/my/Books
sap-snowflake-at: 2024-01-15T10:30:00Z

The adapter injects a Snowflake AT clause into the generated SQL:

SELECT * FROM "DB"."SCHEMA"."BOOKS"
  AT (TIMESTAMP => '2024-01-15T10:30:00Z'::TIMESTAMP_TZ)
WHERE ...

Data retention must be configured on the table (Snowflake default is 1 day for standard edition):

@Snowflake.dataRetentionDays: 7
entity Orders : cuid, managed { ... }

DDL generated (post-CREATE):

ALTER TABLE ORDERS SET DATA_RETENTION_TIME_IN_DAYS = 7

Set @Snowflake.dataRetentionDays: 0 to disable Time Travel for a table.

Search Optimization

Enable Snowflake's Search Optimization Service for fast point-lookup queries on large tables:

@Snowflake.searchOptimized: true
entity Products : cuid, managed {
  sku   : String(50);
  name  : String(200);
}

DDL generated (post-CREATE):

ALTER TABLE PRODUCTS ADD SEARCH OPTIMIZATION

Note: Search Optimization requires Snowflake Enterprise Edition or higher and incurs additional storage cost.

Column Masking Policies

Apply a Snowflake Dynamic Data Masking policy to protect sensitive column values. The policy must already exist in Snowflake before deployment.

entity Customers : cuid {
  name  : String(100);
  email : String(200)  @Snowflake.maskingPolicy: 'MY_SCHEMA.EMAIL_MASK';
  ssn   : String(11)   @Snowflake.maskingPolicy: 'MY_SCHEMA.SSN_MASK';
}

DDL generated (post-CREATE, per column):

ALTER TABLE CUSTOMERS MODIFY COLUMN EMAIL SET MASKING POLICY MY_SCHEMA.EMAIL_MASK;
ALTER TABLE CUSTOMERS MODIFY COLUMN SSN   SET MASKING POLICY MY_SCHEMA.SSN_MASK;
Row Access Policies

Enforce row-level security by attaching a Snowflake Row Access Policy. The policy must already exist in Snowflake before deployment.

@Snowflake.rowAccessPolicy: {
  policy : 'MY_SCHEMA.TENANT_ROW_POLICY',
  on     : ['TENANT_ID']
}
entity TenantData : cuid {
  tenantId : String(36);
  payload  : String(500);
}

DDL generated (post-CREATE):

ALTER TABLE TENANTDATA ADD ROW ACCESS POLICY MY_SCHEMA.TENANT_ROW_POLICY ON (TENANT_ID)
Object and Column Tags

Attach Snowflake object tags to tables or individual columns for governance, cost attribution, or data classification:

// Table-level tags
@Snowflake.tags: [
  { key: 'team',        value: 'data-engineering' },
  { key: 'cost-center', value: 'CC-1234' }
]
entity Orders : cuid, managed {

  // Column-level tags
  email : String(200)  @Snowflake.tags: [{ key: 'pii', value: 'true' }];
}

DDL generated (post-CREATE):

-- Table tags
ALTER TABLE ORDERS SET TAG team = 'data-engineering';
ALTER TABLE ORDERS SET TAG cost-center = 'CC-1234';

-- Column tags
ALTER TABLE ORDERS MODIFY COLUMN EMAIL SET TAG pii = 'true';
VARIANT Colon-Path Filters

When a column is typed as Snowflake VARIANT (or annotated @Snowflake.variant: true), OData filter expressions that reference nested paths are automatically translated to Snowflake's colon-path syntax with a ::VARCHAR cast.

entity Events : cuid {
  payload : Json  @Snowflake.variant: true;
}

OData filter:

GET /Events?$filter=payload/status eq 'active'
GET /Events?$filter=payload/nested/key eq 'value'

Generated SQL:

WHERE PAYLOAD:status::VARCHAR = ?
WHERE PAYLOAD:nested:key::VARCHAR = ?

This allows filtering into arbitrarily nested semi-structured data without defining a fixed schema upfront.

External Tables

Expose an external stage (S3, Azure Blob, GCS) as a CDS entity backed by a Snowflake External Table. The stage and file format must already exist in Snowflake.

@Snowflake.external: {
  stage      : 'MY_STAGE',
  fileFormat : 'MY_PARQUET_FORMAT',
  pattern    : '.*\\.parquet'
}
entity RawEvents {
  key ID : UUID;
  // columns map to external file columns via Snowflake schema-on-read
}

DDL generated (instead of CREATE TABLE):

CREATE EXTERNAL TABLE IF NOT EXISTS "DB"."SCHEMA"."RAWEVENTS"
  WITH LOCATION = @MY_STAGE
  PATTERN = '.*\.parquet'
  FILE_FORMAT = (FORMAT_NAME = 'MY_PARQUET_FORMAT')

CDS Type Mappings

CDS TypeSnowflake DDLNotes
String(n)VARCHAR(n)Default length: 5000
LargeStringTEXT
BooleanBOOLEAN
IntegerNUMBER(38,0)
Integer64NUMBER(38,0)
Decimal(p,s)NUMBER(p,s)
DoubleFLOAT
DateDATE
TimeTIME
DateTimeTIMESTAMP_NTZNo timezone
TimestampTIMESTAMP_TZWith timezone
UUIDVARCHAR(36)
BinaryBINARY
ArrayARRAY
JsonVARIANT
Vector(n)VECTOR(FLOAT, n)n = dimension count; @Snowflake.vector annotation optional

Identifier Handling

Snowflake stores unquoted identifiers as uppercase. The adapter uses the CDS entity name convention (dots replaced by underscores, all uppercase) for physical table names, with an application-configurable prefix:

CDS EntityPhysical Table (with prefix CAP_APP)
my.service.BooksCAP_APP_MY_SERVICE_BOOKS
db.CatalogsCAP_APP_DB_CATALOGS

Mixed-case column names that require exact-case preservation are automatically double-quoted in generated SQL.


Limitations

The following are known Snowflake-specific constraints that affect adapter behaviour:

ItemDetail
Constraints not enforcedSnowflake NOT NULL, UNIQUE, and FOREIGN KEY constraints are metadata-only and not enforced at DML time. CAP's own validation layer handles mandatory fields and integrity checks.
No row-level locksFOR UPDATE / FOR SHARE semantics are not available on Snowflake. Draft concurrency relies on draft table state rather than database locks.
SQL API transactionsThe SQL API (JWT) mode does not support multi-statement transactions. Use SDK mode when full transaction isolation is required.
Add-only schema evolutioncds deploy adds new columns but does not remove or rename existing columns. Structural renames require manual migration SQL.
Streaming / LargeBinaryBinary content is stored as hex-encoded BINARY columns. Byte-range streaming is not supported.
Search Optimization cost@Snowflake.searchOptimized requires Enterprise Edition and adds storage overhead. Evaluate before applying broadly.
External table reads onlyEntities annotated with @Snowflake.external are read-only; INSERT/UPDATE/DELETE against external tables is not supported by Snowflake.
Masking/row policies pre-exist@Snowflake.maskingPolicy and @Snowflake.rowAccessPolicy reference policies that must be created in Snowflake before cds deploy is run.

Troubleshooting

Failed to generate JWT
  • Verify the private key is in PKCS#8 PEM format.
  • Confirm that env:SNOWFLAKE_PRIVATE_KEY resolves to the full key including -----BEGIN PRIVATE KEY----- headers.
  • If the key is passphrase-protected, provide privateKeyPassphrase.
Authentication failed / 390144
  • The public key registered in Snowflake must match the private key used by the adapter.
  • Run DESCRIBE USER CAP_USER; in Snowflake and verify RSA_PUBLIC_KEY_FP is set.
  • Account and user identifiers are case-sensitive in JWT claims.
Object '<database>.<schema>.<table>' does not exist
  • The physical table name is derived from the CDS entity name. Enable DEBUG=sql to inspect the generated statement and compare with SHOW TABLES output in Snowflake.
  • Verify that the role in use has SELECT / INSERT / UPDATE / DELETE privileges on the schema.
Masking policy does not exist during deploy
  • The masking policy referenced by @Snowflake.maskingPolicy must be created in Snowflake before running cds deploy. Create the policy first, then deploy.
Insufficient privileges
GRANT USAGE ON DATABASE CAP_DB TO ROLE CAP_ROLE;
GRANT USAGE ON SCHEMA CAP_DB.APP TO ROLE CAP_ROLE;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA CAP_DB.APP TO ROLE CAP_ROLE;
GRANT USAGE ON WAREHOUSE CAP_WH TO ROLE CAP_ROLE;

Example Projects

Two ready-to-run examples are in the examples/ directory:

ExamplePathDescription
Bookshop (primary DB)examples/cap-svc/Snowflake is the only DB. Standard CAP projections — no custom handler needed. Includes CSV seed data and a local SQLite dev profile.
Analytics (secondary DB)examples/snowflake-secondary-db/HANA is the primary DB; Snowflake is a named secondary service for read-only analytics. Mirrors the pattern used in production apps.

Each example has its own README.md with setup steps.


Development

Build
npm install
npm run build          # compiles src/ → dist/ (required for cds serve)
Tests
npm run test:unit      # 364 unit tests — no Snowflake connection required
npm run test:integ     # 55 integration tests — requires .cdsrc-private.json
npm run test:e2e       # 119 end-to-end HTTP tests — requires live Snowflake
npm test               # runs all three suites in sequence

For end-to-end tests, place credentials in test/e2e/fixtures/.cdsrc-private.json:

{
  "requires": {
    "db": {
      "credentials": {
        "account": "...",
        "user": "...",
        "database": "...",
        "schema": "...",
        "auth": "jwt",
        "jwt": { "privateKey": "env:SNOWFLAKE_PRIVATE_KEY" }
      }
    }
  }
}
Debugging
DEBUG=sql npm run test:e2e        # prints all generated SQL statements
DEBUG=* npm run test:e2e          # full CAP framework debug output
Lint
npm run lint

License

Apache 2.0


Support

Apache-2.0, read

History

Latest

v1.1.0

  1. v1.0.0

On npm
1downloads per week, npm
Weekly downloads for cap-snowflake26 complete calendar weeks from 2026-04-20 to 2026-10-18; the latest total is 1.20 Apr – 26 Apr: 027 Apr – 3 May: 04 May – 10 May: 011 May – 17 May: 12418 May – 24 May: 425 May – 31 May: 61 Jun – 7 Jun: 48 Jun – 14 Jun: 16115 Jun – 21 Jun: 1122 Jun – 28 Jun: 729 Jun – 5 Jul: 186 Jul – 12 Jul: 813 Jul – 19 Jul: 620 Jul – 26 Jul: 327 Jul – 2 Aug: 13 Aug – 9 Aug: 410 Aug – 16 Aug: 717 Aug – 23 Aug: 424 Aug – 30 Aug: 731 Aug – 6 Sept: 47 Sept – 13 Sept: 714 Sept – 20 Sept: 521 Sept – 27 Sept: 528 Sept – 4 Oct: 15 Oct – 11 Oct: 112 Oct – 18 Oct: 1
Database connectors
  1. SAP HANA CloudManaged SAP HANA database service on BTP
  2. PostgreSQL on SAP BTP, hyperscaler optionManaged PostgreSQL from the hyperscaler via a BTP service binding
  3. com.sap.cds:cds-feature-hanaSAP HANA Cloud datasource for CAP Java applications
  4. com.sap.cds:cds-feature-jdbcJDBC runtime for H2 and SQLite in CAP Java
  5. com.sap.cds:cds-feature-postgresqlPostgreSQL datasource for CAP Java applications
  6. com.sap.cds:cds-services-bomMaven bill of materials pinning CAP Java module versions
  7. @cap-js/db-serviceBase service shared by CAP's SQLite, PostgreSQL and HANA services
  8. @cap-js/hanaSAP HANA database service for CAP
  9. @cap-js/postgresPostgreSQL database service for CAP
  10. @cap-js/sqliteSQLite database service for CAP, in memory during development
  11. cap-postgres-btp-connectivityTunnels CAP Node.js to a private PostgreSQL via Cloud Connector
  12. cds-mongodbMongoDB as a CAP database service through Mongoose
  13. @cds-pgroll/postgresCLI for DROP and RENAME schema changes beside @cap-js/postgres
  14. @neoimpulse/cap-js-restore-model-defined-typesCAP plugin casting read values back to CDS model types
  15. @quadrio/cds-db2DB2 adapter for CDS
  16. @sap/cds-hanaDatabase adapter for SAP HANA in CAP
  17. @sap/cdsCAP Node.js runtime
  18. @sap/hana-clientSAP's Node.js driver for SAP HANA
  19. SAP HANASAP's relational database platform, on-premise and in HANA Cloud
  20. cds-pgPostgreSQL adapter for SAP CDS from the SAP Mentors community
  21. cds-mysqlMySQL and MariaDB database adapter for CAP

Details

Sources4
GitHub APIdiscovered here, sleibachchecked
npm registry, cap-snowflakeversion, releases, installschecked
github.com/git+https://github.com/sleibach/cap-snowflake.gittree, pushes, contributors, CIchecked
README.mdquoted, and what the entry declareschecked

Sources are graph entities: the same page may back several entries, and an unreachable source flags every row that rests on it.

Compatibility and provenance
runtimesnode
declared for>=9.0.0
latestv1.1.0
first release
releases and cadence2
discovered viaGitHub API · sleibach
first seen in the atlas
ecosystem and domaincap, data-and-persistence
History in the atlas1
  1. First seen.
For your agent
Ref
artifact:sleibach-cap-snowflake
JSON
/artifacts/sleibach-cap-snowflake.json
MCP
Connecting an agent