UNPKG

@budibase/server

Version:
187 lines (168 loc) 4.3 kB
import { ConnectionInfo, DatasourceFeature, DatasourceFieldType, Integration, QueryType, SqlQuery, } from "@budibase/types" import snowflakeSdk, { SnowflakeError } from "snowflake-sdk" import { promisify } from "util" interface SnowflakeConfig { account: string username: string /** @deprecated Use privateKey instead */ password: string | undefined privateKey: string authenticator: string warehouse: string database: string schema: string } const SCHEMA: Integration = { docs: "https://developers.snowflake.com/", description: "Snowflake is a solution for data warehousing, data lakes, data engineering, data science, data application development, and securely sharing and consuming shared data.", friendlyName: "Snowflake", warningMessage: "Snowflake has announced upcoming security policy changes that will disallow passwords for all non-human service by October 2026. In order to avoid any disruption, please consider using key pair authentication.", type: "Relational", features: { [DatasourceFeature.CONNECTION_CHECKING]: true, }, datasource: { account: { type: DatasourceFieldType.STRING, required: true, }, username: { type: DatasourceFieldType.STRING, required: true, }, password: { type: DatasourceFieldType.PASSWORD, required: false, deprecated: true, }, privateKey: { display: "Private Key (PEM)", type: DatasourceFieldType.SENSITIVE_LONGFORM, required: false, }, authenticator: { type: DatasourceFieldType.STRING, required: false, hidden: "true", }, role: { type: DatasourceFieldType.STRING, }, warehouse: { type: DatasourceFieldType.STRING, required: true, }, database: { type: DatasourceFieldType.STRING, required: true, }, schema: { type: DatasourceFieldType.STRING, required: true, }, }, query: { create: { type: QueryType.SQL, }, read: { type: QueryType.SQL, }, update: { type: QueryType.SQL, }, delete: { type: QueryType.SQL, }, }, } class SnowflakePromise { config: SnowflakeConfig client?: snowflakeSdk.Connection constructor(config: SnowflakeConfig) { this.config = config } async connect() { if (this.client?.isUp()) return this.config.authenticator = "SNOWFLAKE" if (this.config.privateKey) { this.config.authenticator = "SNOWFLAKE_JWT" this.config.privateKey = this.config.privateKey?.trim() } this.client = snowflakeSdk.createConnection(this.config) const connectAsync = promisify(this.client.connect.bind(this.client)) return connectAsync() } async execute(sql: string) { return new Promise((resolve, reject) => { if (!this.client) { throw Error( "No snowflake client present to execute query. Run connect() first to initialise." ) } this.client.execute({ sqlText: sql, complete: function ( err: SnowflakeError | undefined, statementExecuted: any, rows: any ) { if (err) { return reject(err) } resolve(rows) }, }) }) } } class SnowflakeIntegration { private client: SnowflakePromise constructor(config: SnowflakeConfig) { this.client = new SnowflakePromise(config) } async testConnection(): Promise<ConnectionInfo> { try { await this.client.connect() return { connected: true } } catch (e: any) { return { connected: false, error: e.message as string, } } } async internalQuery(query: SqlQuery) { await this.client.connect() try { return await this.client.execute(query.sql) } catch (err: any) { throw err?.message.split(":")[1] || err?.message } } async create(query: SqlQuery) { return this.internalQuery(query) } async read(query: SqlQuery) { return this.internalQuery(query) } async update(query: SqlQuery) { return this.internalQuery(query) } async delete(query: SqlQuery) { return this.internalQuery(query) } } export default { schema: SCHEMA, integration: SnowflakeIntegration, }