@keystrokehq/snowflake 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +247 -0
- package/dist/_official/index.d.mts +2 -0
- package/dist/_official/index.mjs +3 -0
- package/dist/_runtime/index.d.mts +1 -0
- package/dist/_runtime/index.mjs +1 -0
- package/dist/accounts.d.mts +40 -0
- package/dist/accounts.mjs +51 -0
- package/dist/bulk.d.mts +71 -0
- package/dist/bulk.mjs +65 -0
- package/dist/catalog.d.mts +144 -0
- package/dist/catalog.mjs +158 -0
- package/dist/client.d.mts +44 -0
- package/dist/client.mjs +273 -0
- package/dist/common-BnKTPQXc.mjs +92 -0
- package/dist/connection.d.mts +2 -0
- package/dist/connection.mjs +3 -0
- package/dist/databases.d.mts +2 -0
- package/dist/databases.mjs +3 -0
- package/dist/errors-DKnc9o_a.mjs +95 -0
- package/dist/events.d.mts +382 -0
- package/dist/events.mjs +291 -0
- package/dist/file-formats.d.mts +39 -0
- package/dist/file-formats.mjs +48 -0
- package/dist/functions.d.mts +53 -0
- package/dist/functions.mjs +56 -0
- package/dist/grants.d.mts +93 -0
- package/dist/grants.mjs +102 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +1 -0
- package/dist/integration-CCWMSTlO.d.mts +170 -0
- package/dist/integration-Dptv8L0L.mjs +207 -0
- package/dist/org-admin.d.mts +80 -0
- package/dist/org-admin.mjs +79 -0
- package/dist/pipes.d.mts +119 -0
- package/dist/pipes.mjs +106 -0
- package/dist/procedures.d.mts +61 -0
- package/dist/procedures.mjs +79 -0
- package/dist/results.d.mts +16 -0
- package/dist/results.mjs +64 -0
- package/dist/retry-0hMfb8cC.mjs +164 -0
- package/dist/retry-w7cTp1QL.d.mts +10 -0
- package/dist/roles.d.mts +60 -0
- package/dist/roles.mjs +74 -0
- package/dist/rows.d.mts +106 -0
- package/dist/rows.mjs +223 -0
- package/dist/schemas/index.d.mts +2 -0
- package/dist/schemas/index.mjs +4 -0
- package/dist/schemas-catalog.d.mts +2 -0
- package/dist/schemas-catalog.mjs +3 -0
- package/dist/shares.d.mts +56 -0
- package/dist/shares.mjs +77 -0
- package/dist/sql-options-2k5xQ-oS.d.mts +32 -0
- package/dist/sql-options-DI-OmLU6.mjs +79 -0
- package/dist/sql-safety-CywdR3EP.mjs +56 -0
- package/dist/sql.d.mts +84 -0
- package/dist/sql.mjs +209 -0
- package/dist/stages.d.mts +64 -0
- package/dist/stages.mjs +81 -0
- package/dist/statements-B2ThF_4b.mjs +81 -0
- package/dist/statements-DJL0qVNA.d.mts +238 -0
- package/dist/status-page.d.mts +510 -0
- package/dist/status-page.mjs +261 -0
- package/dist/streaming.d.mts +70 -0
- package/dist/streaming.mjs +56 -0
- package/dist/streams.d.mts +71 -0
- package/dist/streams.mjs +78 -0
- package/dist/tables.d.mts +2 -0
- package/dist/tables.mjs +3 -0
- package/dist/tasks.d.mts +79 -0
- package/dist/tasks.mjs +104 -0
- package/dist/triggers.d.mts +578 -0
- package/dist/triggers.mjs +1363 -0
- package/dist/users.d.mts +61 -0
- package/dist/users.mjs +67 -0
- package/dist/verification.d.mts +201 -0
- package/dist/verification.mjs +512 -0
- package/dist/views.d.mts +2 -0
- package/dist/views.mjs +3 -0
- package/dist/warehouses.d.mts +68 -0
- package/dist/warehouses.mjs +101 -0
- package/package.json +190 -0
package/dist/rows.mjs
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { executeSql } from "./sql.mjs";
|
|
2
|
+
import { i as quoteIdentifier, t as buildFqn } from "./sql-safety-CywdR3EP.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/rows.ts
|
|
5
|
+
function isSqlBinding(value) {
|
|
6
|
+
return typeof value === "object" && value !== null && "type" in value && "value" in value && typeof value.type === "string";
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Map a JS value to its Snowflake typed binding. Callers who need full
|
|
10
|
+
* control can pre-build a `{type, value}` object and pass it through.
|
|
11
|
+
*/
|
|
12
|
+
function inferBinding(value) {
|
|
13
|
+
if (isSqlBinding(value)) return value;
|
|
14
|
+
if (value === null || value === void 0) return {
|
|
15
|
+
type: "TEXT",
|
|
16
|
+
value: null
|
|
17
|
+
};
|
|
18
|
+
if (typeof value === "string") return {
|
|
19
|
+
type: "TEXT",
|
|
20
|
+
value
|
|
21
|
+
};
|
|
22
|
+
if (typeof value === "boolean") return {
|
|
23
|
+
type: "BOOLEAN",
|
|
24
|
+
value: value ? "true" : "false"
|
|
25
|
+
};
|
|
26
|
+
if (typeof value === "bigint") return {
|
|
27
|
+
type: "FIXED",
|
|
28
|
+
value: value.toString()
|
|
29
|
+
};
|
|
30
|
+
if (typeof value === "number") {
|
|
31
|
+
if (Number.isNaN(value) || !Number.isFinite(value)) throw new TypeError("Cannot bind NaN or Infinity as a Snowflake value");
|
|
32
|
+
return Number.isInteger(value) ? {
|
|
33
|
+
type: "FIXED",
|
|
34
|
+
value: value.toString()
|
|
35
|
+
} : {
|
|
36
|
+
type: "REAL",
|
|
37
|
+
value: value.toString()
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (value instanceof Date) return {
|
|
41
|
+
type: "TIMESTAMP_LTZ",
|
|
42
|
+
value: value.toISOString()
|
|
43
|
+
};
|
|
44
|
+
const unreachable = value;
|
|
45
|
+
throw new TypeError(`Unsupported binding type: ${typeof unreachable}`);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Mutable binding builder. Internally indexes bindings starting at 1 and
|
|
49
|
+
* returns the `?` placeholder for each append so callers can weave them into
|
|
50
|
+
* their SQL composition.
|
|
51
|
+
*/
|
|
52
|
+
var BindingBuilder = class {
|
|
53
|
+
map = {};
|
|
54
|
+
counter = 0;
|
|
55
|
+
add(value) {
|
|
56
|
+
this.counter += 1;
|
|
57
|
+
this.map[String(this.counter)] = inferBinding(value);
|
|
58
|
+
return "?";
|
|
59
|
+
}
|
|
60
|
+
toRecord() {
|
|
61
|
+
return this.map;
|
|
62
|
+
}
|
|
63
|
+
size() {
|
|
64
|
+
return this.counter;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
function composeWhere(where, bindings) {
|
|
68
|
+
const clauses = [];
|
|
69
|
+
for (const [column, value] of Object.entries(where)) {
|
|
70
|
+
if (value === null || value === void 0) {
|
|
71
|
+
clauses.push(`${quoteIdentifier(column)} IS NULL`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
clauses.push(`${quoteIdentifier(column)} = ${bindings.add(value)}`);
|
|
75
|
+
}
|
|
76
|
+
return clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "";
|
|
77
|
+
}
|
|
78
|
+
function composeSet(set, bindings) {
|
|
79
|
+
const fragments = Object.entries(set).map(([column, value]) => `${quoteIdentifier(column)} = ${bindings.add(value)}`);
|
|
80
|
+
if (fragments.length === 0) throw new Error("updateRow requires at least one column in `set`");
|
|
81
|
+
return fragments.join(", ");
|
|
82
|
+
}
|
|
83
|
+
function executeRowStatement(client, sql, bindings, ctx) {
|
|
84
|
+
return executeSql(client, {
|
|
85
|
+
statement: sql,
|
|
86
|
+
warehouse: ctx.warehouse,
|
|
87
|
+
role: ctx.role,
|
|
88
|
+
database: ctx.database,
|
|
89
|
+
schema: ctx.schema,
|
|
90
|
+
bindings: bindings.size() > 0 ? bindings.toRecord() : void 0
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Read the first numeric value from the first row of a DML result.
|
|
95
|
+
* Snowflake returns INSERT/UPDATE/DELETE stats as a single row with a
|
|
96
|
+
* column like `"number of rows inserted"`.
|
|
97
|
+
*/
|
|
98
|
+
function readAffectedRows(result) {
|
|
99
|
+
if (result.rows.length === 0) return 0;
|
|
100
|
+
const firstRow = result.rows[0];
|
|
101
|
+
if (!firstRow) return 0;
|
|
102
|
+
const values = Array.isArray(firstRow) ? firstRow : Object.values(firstRow);
|
|
103
|
+
for (const value of values) {
|
|
104
|
+
if (typeof value === "number") return value;
|
|
105
|
+
if (typeof value === "bigint") return Number(value);
|
|
106
|
+
if (typeof value === "string") {
|
|
107
|
+
const parsed = Number(value);
|
|
108
|
+
if (!Number.isNaN(parsed)) return parsed;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
function composeColumnList(columns) {
|
|
114
|
+
if (!columns || columns.length === 0) return "*";
|
|
115
|
+
return columns.map((c) => quoteIdentifier(c)).join(", ");
|
|
116
|
+
}
|
|
117
|
+
function composeOrderBy(orderBy) {
|
|
118
|
+
if (!orderBy || orderBy.length === 0) return "";
|
|
119
|
+
return ` ORDER BY ${orderBy.map(({ column, direction }) => {
|
|
120
|
+
const dir = direction === "DESC" ? " DESC" : " ASC";
|
|
121
|
+
return `${quoteIdentifier(column)}${dir}`;
|
|
122
|
+
}).join(", ")}`;
|
|
123
|
+
}
|
|
124
|
+
function composeLimitOffset(limit, offset) {
|
|
125
|
+
let out = "";
|
|
126
|
+
if (typeof limit === "number" && limit >= 0) out += ` LIMIT ${Math.floor(limit)}`;
|
|
127
|
+
if (typeof offset === "number" && offset > 0) out += ` OFFSET ${Math.floor(offset)}`;
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
async function findRows(client, options) {
|
|
131
|
+
const bindings = new BindingBuilder();
|
|
132
|
+
return (await executeRowStatement(client, `SELECT ${composeColumnList(options.columns)} FROM ${buildFqn(options.database, options.schema, options.table)}` + composeWhere(options.where ?? {}, bindings) + composeOrderBy(options.orderBy) + composeLimitOffset(options.limit, options.offset), bindings, options)).rows;
|
|
133
|
+
}
|
|
134
|
+
async function findRow(client, options) {
|
|
135
|
+
return (await findRows(client, {
|
|
136
|
+
...options,
|
|
137
|
+
limit: 1
|
|
138
|
+
}))[0];
|
|
139
|
+
}
|
|
140
|
+
async function insertRow(client, options) {
|
|
141
|
+
const columns = Object.keys(options.row);
|
|
142
|
+
if (columns.length === 0) throw new Error("insertRow requires at least one column in `row`");
|
|
143
|
+
const bindings = new BindingBuilder();
|
|
144
|
+
const placeholders = columns.map((column) => bindings.add(options.row[column]));
|
|
145
|
+
return readAffectedRows(await executeRowStatement(client, `INSERT INTO ${buildFqn(options.database, options.schema, options.table)} (${columns.map((c) => quoteIdentifier(c)).join(", ")}) VALUES (${placeholders.join(", ")})`, bindings, options));
|
|
146
|
+
}
|
|
147
|
+
async function batchInsert(client, options) {
|
|
148
|
+
if (options.rows.length === 0) return 0;
|
|
149
|
+
const first = options.rows[0];
|
|
150
|
+
if (!first) return 0;
|
|
151
|
+
const columns = options.columns ?? Object.keys(first);
|
|
152
|
+
if (columns.length === 0) throw new Error("batchInsert requires at least one column");
|
|
153
|
+
const bindings = new BindingBuilder();
|
|
154
|
+
const valueTuples = options.rows.map((row) => {
|
|
155
|
+
return `(${columns.map((column) => bindings.add(row[column])).join(", ")})`;
|
|
156
|
+
});
|
|
157
|
+
return readAffectedRows(await executeRowStatement(client, `INSERT INTO ${buildFqn(options.database, options.schema, options.table)} (${columns.map((c) => quoteIdentifier(c)).join(", ")}) VALUES ${valueTuples.join(", ")}`, bindings, options));
|
|
158
|
+
}
|
|
159
|
+
async function updateRow(client, options) {
|
|
160
|
+
const bindings = new BindingBuilder();
|
|
161
|
+
const setClause = composeSet(options.set, bindings);
|
|
162
|
+
const whereClause = composeWhere(options.where, bindings);
|
|
163
|
+
return readAffectedRows(await executeRowStatement(client, `UPDATE ${buildFqn(options.database, options.schema, options.table)} SET ${setClause}${whereClause}`, bindings, options));
|
|
164
|
+
}
|
|
165
|
+
async function deleteRow(client, options) {
|
|
166
|
+
if (Object.keys(options.where).length === 0) throw new Error("deleteRow refuses an empty where filter; call executeSql to truncate intentionally");
|
|
167
|
+
const bindings = new BindingBuilder();
|
|
168
|
+
const whereClause = composeWhere(options.where, bindings);
|
|
169
|
+
return readAffectedRows(await executeRowStatement(client, `DELETE FROM ${buildFqn(options.database, options.schema, options.table)}${whereClause}`, bindings, options));
|
|
170
|
+
}
|
|
171
|
+
function readMergeStats(result) {
|
|
172
|
+
if (result.rows.length === 0) return {
|
|
173
|
+
inserted: 0,
|
|
174
|
+
updated: 0
|
|
175
|
+
};
|
|
176
|
+
const firstRow = result.rows[0];
|
|
177
|
+
if (!firstRow) return {
|
|
178
|
+
inserted: 0,
|
|
179
|
+
updated: 0
|
|
180
|
+
};
|
|
181
|
+
const entries = Array.isArray(firstRow) ? result.columns.map((col, idx) => [col.name, firstRow[idx]]) : Object.entries(firstRow);
|
|
182
|
+
let inserted = 0;
|
|
183
|
+
let updated = 0;
|
|
184
|
+
for (const [name, value] of entries) {
|
|
185
|
+
const numeric = typeof value === "number" ? value : Number(value);
|
|
186
|
+
if (!Number.isFinite(numeric)) continue;
|
|
187
|
+
const key = name.toLowerCase();
|
|
188
|
+
if (key.includes("insert")) inserted = numeric;
|
|
189
|
+
else if (key.includes("update")) updated = numeric;
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
inserted,
|
|
193
|
+
updated
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
async function upsertRow(client, options) {
|
|
197
|
+
const rowColumns = Object.keys(options.row);
|
|
198
|
+
if (rowColumns.length === 0) throw new Error("upsertRow requires at least one column in `row`");
|
|
199
|
+
if (options.matchColumns.length === 0) throw new Error("upsertRow requires at least one match column");
|
|
200
|
+
for (const column of options.matchColumns) if (!rowColumns.includes(column)) throw new Error(`upsertRow match column "${column}" is missing from row`);
|
|
201
|
+
const bindings = new BindingBuilder();
|
|
202
|
+
const srcTuples = rowColumns.map((column) => bindings.add(options.row[column]));
|
|
203
|
+
const fqn = buildFqn(options.database, options.schema, options.table);
|
|
204
|
+
rowColumns.map((c) => quoteIdentifier(c)).join(", ");
|
|
205
|
+
const matchPredicate = options.matchColumns.map((column) => `t.${quoteIdentifier(column)} = s.${quoteIdentifier(column)}`).join(" AND ");
|
|
206
|
+
const updateSet = (options.updateColumns ?? rowColumns.filter((c) => !options.matchColumns.includes(c))).map((column) => `${quoteIdentifier(column)} = s.${quoteIdentifier(column)}`).join(", ");
|
|
207
|
+
const insertCols = rowColumns.map((c) => quoteIdentifier(c)).join(", ");
|
|
208
|
+
const insertVals = rowColumns.map((c) => `s.${quoteIdentifier(c)}`).join(", ");
|
|
209
|
+
return readMergeStats(await executeRowStatement(client, `MERGE INTO ${fqn} t USING (SELECT ${srcTuples.map((placeholder, idx) => `${placeholder} AS ${quoteIdentifier(rowColumns[idx] ?? "")}`).join(", ")}) s ON ${matchPredicate}` + (updateSet ? ` WHEN MATCHED THEN UPDATE SET ${updateSet}` : "") + ` WHEN NOT MATCHED THEN INSERT (${insertCols}) VALUES (${insertVals})`, bindings, options));
|
|
210
|
+
}
|
|
211
|
+
async function mergeRows(client, options) {
|
|
212
|
+
return readMergeStats(await executeSql(client, {
|
|
213
|
+
statement: options.statement,
|
|
214
|
+
warehouse: options.warehouse,
|
|
215
|
+
role: options.role,
|
|
216
|
+
database: options.database,
|
|
217
|
+
schema: options.schema,
|
|
218
|
+
bindings: options.bindings
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
//#endregion
|
|
223
|
+
export { BindingBuilder, batchInsert, deleteRow, findRow, findRows, inferBinding, insertRow, mergeRows, updateRow, upsertRow };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { C as resultSetMetaDataSchema, D as sqlBindingValueSchema, E as snowflakeObjectRowSchema, O as sqlBindingsSchema, S as partitionInfoSchema, T as snowflakeIdentifier, _ as SnowflakeObjectRow, a as SubmitStatementInput, b as StatementContext, c as statementStatus, d as HydratedRow, f as PartitionResponse, g as ResultSetMetaData, h as PartitionInfo, i as StatementTerminalStatus, k as statementContextSchema, l as statementStatusCodeSchema, m as ColumnMeta, n as StatementResponse, o as cancelStatementResponseSchema, p as partitionResponseSchema, r as StatementStatus, s as statementResponseSchema, t as CancelStatementResponse, u as submitStatementInputSchema, v as SqlBinding, w as snowflakeFqn, x as columnMetaSchema, y as SqlBindings } from "../statements-DJL0qVNA.mjs";
|
|
2
|
+
export { CancelStatementResponse, ColumnMeta, HydratedRow, PartitionInfo, PartitionResponse, ResultSetMetaData, SnowflakeObjectRow, SqlBinding, SqlBindings, StatementContext, StatementResponse, StatementStatus, StatementTerminalStatus, SubmitStatementInput, cancelStatementResponseSchema, columnMetaSchema, partitionInfoSchema, partitionResponseSchema, resultSetMetaDataSchema, snowflakeFqn, snowflakeIdentifier, snowflakeObjectRowSchema, sqlBindingValueSchema, sqlBindingsSchema, statementContextSchema, statementResponseSchema, statementStatus, statementStatusCodeSchema, submitStatementInputSchema };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { a as snowflakeIdentifier, c as sqlBindingsSchema, i as snowflakeFqn, l as statementContextSchema, n as partitionInfoSchema, o as snowflakeObjectRowSchema, r as resultSetMetaDataSchema, s as sqlBindingValueSchema, t as columnMetaSchema } from "../common-BnKTPQXc.mjs";
|
|
2
|
+
import { a as submitStatementInputSchema, i as statementStatusCodeSchema, n as statementResponseSchema, o as partitionResponseSchema, r as statementStatus, t as cancelStatementResponseSchema } from "../statements-B2ThF_4b.mjs";
|
|
3
|
+
|
|
4
|
+
export { cancelStatementResponseSchema, columnMetaSchema, partitionInfoSchema, partitionResponseSchema, resultSetMetaDataSchema, snowflakeFqn, snowflakeIdentifier, snowflakeObjectRowSchema, sqlBindingValueSchema, sqlBindingsSchema, statementContextSchema, statementResponseSchema, statementStatus, statementStatusCodeSchema, submitStatementInputSchema };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { SnowflakeClient } from "./client.mjs";
|
|
2
|
+
import { d as HydratedRow } from "./statements-DJL0qVNA.mjs";
|
|
3
|
+
import { CatalogResult, ShowOptions, showShares } from "./catalog.mjs";
|
|
4
|
+
|
|
5
|
+
//#region src/shares.d.ts
|
|
6
|
+
interface ShareContext {
|
|
7
|
+
readonly warehouse?: string;
|
|
8
|
+
readonly role?: string;
|
|
9
|
+
}
|
|
10
|
+
interface CreateShareOptions extends ShareContext {
|
|
11
|
+
readonly name: string;
|
|
12
|
+
readonly orReplace?: boolean;
|
|
13
|
+
readonly ifNotExists?: boolean;
|
|
14
|
+
readonly comment?: string;
|
|
15
|
+
}
|
|
16
|
+
declare function createShare(client: SnowflakeClient, options: CreateShareOptions): Promise<void>;
|
|
17
|
+
interface AlterShareOptions extends ShareContext {
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly ifExists?: boolean;
|
|
20
|
+
/** Replace the full account list via `SET ACCOUNTS = (...)`. */
|
|
21
|
+
readonly setAccounts?: readonly string[];
|
|
22
|
+
/** `ADD ACCOUNTS = (...)`. */
|
|
23
|
+
readonly addAccounts?: readonly string[];
|
|
24
|
+
/** `REMOVE ACCOUNTS = (...)`. */
|
|
25
|
+
readonly removeAccounts?: readonly string[];
|
|
26
|
+
/** `SET COMMENT = '...'`. */
|
|
27
|
+
readonly comment?: string;
|
|
28
|
+
/** `UNSET COMMENT`. */
|
|
29
|
+
readonly unsetComment?: boolean;
|
|
30
|
+
}
|
|
31
|
+
declare function alterShare(client: SnowflakeClient, options: AlterShareOptions): Promise<void>;
|
|
32
|
+
interface DropShareOptions extends ShareContext {
|
|
33
|
+
readonly name: string;
|
|
34
|
+
readonly ifExists?: boolean;
|
|
35
|
+
}
|
|
36
|
+
declare function dropShare(client: SnowflakeClient, options: DropShareOptions): Promise<void>;
|
|
37
|
+
interface DescribeShareOptions extends ShareContext {
|
|
38
|
+
readonly name: string;
|
|
39
|
+
}
|
|
40
|
+
declare function describeShare(client: SnowflakeClient, options: DescribeShareOptions): Promise<readonly HydratedRow[]>;
|
|
41
|
+
interface AddAccountsToShareOptions extends ShareContext {
|
|
42
|
+
readonly name: string;
|
|
43
|
+
readonly accounts: readonly string[];
|
|
44
|
+
readonly ifExists?: boolean;
|
|
45
|
+
/** Optional `SHARE_RESTRICTIONS = TRUE|FALSE` appended to ADD ACCOUNTS. */
|
|
46
|
+
readonly shareRestrictions?: boolean;
|
|
47
|
+
}
|
|
48
|
+
declare function addAccountsToShare(client: SnowflakeClient, options: AddAccountsToShareOptions): Promise<void>;
|
|
49
|
+
interface RemoveAccountsFromShareOptions extends ShareContext {
|
|
50
|
+
readonly name: string;
|
|
51
|
+
readonly accounts: readonly string[];
|
|
52
|
+
readonly ifExists?: boolean;
|
|
53
|
+
}
|
|
54
|
+
declare function removeAccountsFromShare(client: SnowflakeClient, options: RemoveAccountsFromShareOptions): Promise<void>;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { AddAccountsToShareOptions, AlterShareOptions, type CatalogResult, CreateShareOptions, DescribeShareOptions, DropShareOptions, RemoveAccountsFromShareOptions, ShareContext, type ShowOptions, addAccountsToShare, alterShare, createShare, describeShare, dropShare, removeAccountsFromShare, showShares };
|
package/dist/shares.mjs
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { executeSql } from "./sql.mjs";
|
|
2
|
+
import { a as quoteLiteral, i as quoteIdentifier } from "./sql-safety-CywdR3EP.mjs";
|
|
3
|
+
import { showShares } from "./catalog.mjs";
|
|
4
|
+
|
|
5
|
+
//#region src/shares.ts
|
|
6
|
+
async function createShare(client, options) {
|
|
7
|
+
const prefix = `CREATE${options.orReplace ? " OR REPLACE" : ""} SHARE${options.ifNotExists ? " IF NOT EXISTS" : ""}`;
|
|
8
|
+
const comment = options.comment ? ` COMMENT = ${quoteLiteral(options.comment)}` : "";
|
|
9
|
+
await executeSql(client, {
|
|
10
|
+
statement: `${prefix} ${quoteIdentifier(options.name)}${comment}`,
|
|
11
|
+
warehouse: options.warehouse,
|
|
12
|
+
role: options.role
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Snowflake account identifiers for shares take the form
|
|
17
|
+
* `<orgName>.<accountName>` (unquoted). We validate the shape lightly
|
|
18
|
+
* and pass it through without quoting because Snowflake treats quoted
|
|
19
|
+
* identifiers here as case-sensitive names which would not match the
|
|
20
|
+
* real account.
|
|
21
|
+
*/
|
|
22
|
+
const ACCOUNT_ID_RE = /^[A-Za-z0-9_]+\.[A-Za-z0-9_]+$/u;
|
|
23
|
+
function renderAccountList(accounts) {
|
|
24
|
+
if (accounts.length === 0) throw new Error("accounts must be a non-empty list");
|
|
25
|
+
for (const a of accounts) if (!ACCOUNT_ID_RE.test(a)) throw new Error(`Invalid account identifier "${a}" — expected "<orgName>.<accountName>" (letters, digits, underscore)`);
|
|
26
|
+
return accounts.join(", ");
|
|
27
|
+
}
|
|
28
|
+
async function alterShare(client, options) {
|
|
29
|
+
let tail;
|
|
30
|
+
if (options.addAccounts) tail = `ADD ACCOUNTS = (${renderAccountList(options.addAccounts)})`;
|
|
31
|
+
else if (options.removeAccounts) tail = `REMOVE ACCOUNTS = (${renderAccountList(options.removeAccounts)})`;
|
|
32
|
+
else if (options.setAccounts) tail = `SET ACCOUNTS = (${renderAccountList(options.setAccounts)})`;
|
|
33
|
+
else if (options.comment !== void 0) tail = `SET COMMENT = ${quoteLiteral(options.comment)}`;
|
|
34
|
+
else if (options.unsetComment) tail = "UNSET COMMENT";
|
|
35
|
+
else throw new Error("alterShare requires one of: setAccounts / addAccounts / removeAccounts / comment / unsetComment");
|
|
36
|
+
await executeSql(client, {
|
|
37
|
+
statement: `ALTER SHARE${options.ifExists ? " IF EXISTS" : ""} ${quoteIdentifier(options.name)} ${tail}`,
|
|
38
|
+
warehouse: options.warehouse,
|
|
39
|
+
role: options.role
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async function dropShare(client, options) {
|
|
43
|
+
await executeSql(client, {
|
|
44
|
+
statement: `DROP SHARE${options.ifExists ? " IF EXISTS" : ""} ${quoteIdentifier(options.name)}`,
|
|
45
|
+
warehouse: options.warehouse,
|
|
46
|
+
role: options.role
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function describeShare(client, options) {
|
|
50
|
+
return (await executeSql(client, {
|
|
51
|
+
statement: `DESCRIBE SHARE ${quoteIdentifier(options.name)}`,
|
|
52
|
+
warehouse: options.warehouse,
|
|
53
|
+
role: options.role
|
|
54
|
+
})).rows;
|
|
55
|
+
}
|
|
56
|
+
async function addAccountsToShare(client, options) {
|
|
57
|
+
const ifExists = options.ifExists ? " IF EXISTS" : "";
|
|
58
|
+
const accounts = renderAccountList(options.accounts);
|
|
59
|
+
const restrictions = options.shareRestrictions !== void 0 ? ` SHARE_RESTRICTIONS = ${options.shareRestrictions ? "TRUE" : "FALSE"}` : "";
|
|
60
|
+
await executeSql(client, {
|
|
61
|
+
statement: `ALTER SHARE${ifExists} ${quoteIdentifier(options.name)} ADD ACCOUNTS = (${accounts})${restrictions}`,
|
|
62
|
+
warehouse: options.warehouse,
|
|
63
|
+
role: options.role
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function removeAccountsFromShare(client, options) {
|
|
67
|
+
const ifExists = options.ifExists ? " IF EXISTS" : "";
|
|
68
|
+
const accounts = renderAccountList(options.accounts);
|
|
69
|
+
await executeSql(client, {
|
|
70
|
+
statement: `ALTER SHARE${ifExists} ${quoteIdentifier(options.name)} REMOVE ACCOUNTS = (${accounts})`,
|
|
71
|
+
warehouse: options.warehouse,
|
|
72
|
+
role: options.role
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
export { addAccountsToShare, alterShare, createShare, describeShare, dropShare, removeAccountsFromShare, showShares };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
//#region src/sql-options.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Option-list composition helpers for Snowflake DDL/DML.
|
|
4
|
+
*
|
|
5
|
+
* Most CREATE/ALTER statements (stages, file formats, pipes, tasks, COPY
|
|
6
|
+
* options, warehouses) accept option lists like:
|
|
7
|
+
*
|
|
8
|
+
* FILE_FORMAT = (TYPE = 'CSV' FIELD_DELIMITER = '|' SKIP_HEADER = 1)
|
|
9
|
+
* CREDENTIALS = (AWS_KEY_ID = 'abc' AWS_SECRET_KEY = 'xyz')
|
|
10
|
+
* COPY_OPTIONS = (ON_ERROR = 'CONTINUE' PURGE = TRUE)
|
|
11
|
+
*
|
|
12
|
+
* These helpers compose those lists safely:
|
|
13
|
+
* - String values are quoted with {@link quoteLiteral}.
|
|
14
|
+
* - Numbers, booleans, and raw-marked values pass through verbatim.
|
|
15
|
+
* - Nested option maps render as parenthesised sub-lists.
|
|
16
|
+
* - Ordering is preserved in insertion order.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* A scalar that can appear on the RHS of an option. Pre-built raw SQL can
|
|
20
|
+
* be passed via {@link rawOption} when callers need to inject a keyword
|
|
21
|
+
* (e.g. `AUTO_REFRESH = TRUE`, `ENCRYPTION = (TYPE = 'SNOWFLAKE_SSE')`).
|
|
22
|
+
*/
|
|
23
|
+
type OptionScalar = string | number | bigint | boolean | null | undefined | RawSql | OptionMap | readonly OptionScalar[];
|
|
24
|
+
interface OptionMap {
|
|
25
|
+
readonly [key: string]: OptionScalar;
|
|
26
|
+
}
|
|
27
|
+
/** Marker type for a pre-formatted SQL fragment. */
|
|
28
|
+
interface RawSql {
|
|
29
|
+
readonly __rawSql: string;
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { OptionMap as t };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { a as quoteLiteral } from "./sql-safety-CywdR3EP.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/sql-options.ts
|
|
4
|
+
/**
|
|
5
|
+
* Option-list composition helpers for Snowflake DDL/DML.
|
|
6
|
+
*
|
|
7
|
+
* Most CREATE/ALTER statements (stages, file formats, pipes, tasks, COPY
|
|
8
|
+
* options, warehouses) accept option lists like:
|
|
9
|
+
*
|
|
10
|
+
* FILE_FORMAT = (TYPE = 'CSV' FIELD_DELIMITER = '|' SKIP_HEADER = 1)
|
|
11
|
+
* CREDENTIALS = (AWS_KEY_ID = 'abc' AWS_SECRET_KEY = 'xyz')
|
|
12
|
+
* COPY_OPTIONS = (ON_ERROR = 'CONTINUE' PURGE = TRUE)
|
|
13
|
+
*
|
|
14
|
+
* These helpers compose those lists safely:
|
|
15
|
+
* - String values are quoted with {@link quoteLiteral}.
|
|
16
|
+
* - Numbers, booleans, and raw-marked values pass through verbatim.
|
|
17
|
+
* - Nested option maps render as parenthesised sub-lists.
|
|
18
|
+
* - Ordering is preserved in insertion order.
|
|
19
|
+
*/
|
|
20
|
+
function isRawSql(value) {
|
|
21
|
+
return typeof value === "object" && value !== null && typeof value.__rawSql === "string";
|
|
22
|
+
}
|
|
23
|
+
function isOptionMap(value) {
|
|
24
|
+
if (value === null || typeof value !== "object") return false;
|
|
25
|
+
if (Array.isArray(value)) return false;
|
|
26
|
+
if (isRawSql(value)) return false;
|
|
27
|
+
if (value instanceof Date) return false;
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
function formatScalar(value) {
|
|
31
|
+
if (value === void 0) return void 0;
|
|
32
|
+
if (value === null) return "NULL";
|
|
33
|
+
if (isRawSql(value)) return value.__rawSql;
|
|
34
|
+
if (typeof value === "string") return quoteLiteral(value);
|
|
35
|
+
if (typeof value === "number") {
|
|
36
|
+
if (!Number.isFinite(value)) throw new TypeError("Cannot serialise NaN/Infinity as a Snowflake option value");
|
|
37
|
+
return value.toString();
|
|
38
|
+
}
|
|
39
|
+
if (typeof value === "bigint") return value.toString();
|
|
40
|
+
if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
|
|
41
|
+
if (Array.isArray(value)) return `(${value.map((item) => formatScalar(item)).filter((item) => item !== void 0).join(", ")})`;
|
|
42
|
+
if (isOptionMap(value)) return formatOptionGroup(value);
|
|
43
|
+
throw new TypeError(`Unsupported Snowflake option value: ${typeof value}`);
|
|
44
|
+
}
|
|
45
|
+
/** Render a map like `(KEY = value KEY2 = 'literal')`. Empty maps render as `()`. */
|
|
46
|
+
function formatOptionGroup(map) {
|
|
47
|
+
const parts = [];
|
|
48
|
+
for (const [key, value] of Object.entries(map)) {
|
|
49
|
+
const rendered = formatScalar(value);
|
|
50
|
+
if (rendered === void 0) continue;
|
|
51
|
+
parts.push(`${key.toUpperCase()} = ${rendered}`);
|
|
52
|
+
}
|
|
53
|
+
return `(${parts.join(" ")})`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Render a map as a space-separated option list (no wrapping parens). This
|
|
57
|
+
* is the form used directly in `CREATE STAGE ... URL='...' CREDENTIALS=(..)`
|
|
58
|
+
* where top-level options sit alongside the object name.
|
|
59
|
+
*/
|
|
60
|
+
function formatOptions(map) {
|
|
61
|
+
const parts = [];
|
|
62
|
+
for (const [key, value] of Object.entries(map)) {
|
|
63
|
+
const rendered = formatScalar(value);
|
|
64
|
+
if (rendered === void 0) continue;
|
|
65
|
+
parts.push(`${key.toUpperCase()} = ${rendered}`);
|
|
66
|
+
}
|
|
67
|
+
return parts.join(" ");
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Render a nested option group as ` KEY = (...)` — returns empty string when
|
|
71
|
+
* the map is undefined so callers can compose optional clauses inline.
|
|
72
|
+
*/
|
|
73
|
+
function renderOptionGroup(key, map) {
|
|
74
|
+
if (!map) return "";
|
|
75
|
+
return ` ${key.toUpperCase()} = ${formatOptionGroup(map)}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
export { renderOptionGroup as n, formatOptions as t };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
//#region src/sql-safety.ts
|
|
2
|
+
/**
|
|
3
|
+
* SQL identifier + literal escaping utilities.
|
|
4
|
+
*
|
|
5
|
+
* Snowflake follows ANSI rules:
|
|
6
|
+
* - Identifiers: wrap in `"..."`; internal `"` is escaped as `""`.
|
|
7
|
+
* - String literals: wrap in `'...'`; internal `'` is escaped as `''`.
|
|
8
|
+
* - Numeric, boolean, null literals are written raw.
|
|
9
|
+
*
|
|
10
|
+
* Every domain module that composes SQL goes through these helpers — they
|
|
11
|
+
* are the package's sole defense against SQL injection from operation
|
|
12
|
+
* inputs that name identifiers or match patterns.
|
|
13
|
+
*/
|
|
14
|
+
const UNQUOTED_IDENT = /^[A-Z_][A-Z0-9_$]*$/u;
|
|
15
|
+
/**
|
|
16
|
+
* Return a double-quoted form of the identifier, escaping embedded
|
|
17
|
+
* double-quotes. When `allowUnquoted` is true and the input is already a
|
|
18
|
+
* canonical unquoted identifier, return it verbatim so the resulting SQL
|
|
19
|
+
* is more readable.
|
|
20
|
+
*/
|
|
21
|
+
function quoteIdentifier(name, options = {}) {
|
|
22
|
+
if (options.allowUnquoted && UNQUOTED_IDENT.test(name)) return name;
|
|
23
|
+
return `"${name.replaceAll("\"", "\"\"")}"`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Build a fully-qualified name from up to three segments. Each segment is
|
|
27
|
+
* independently quoted. Missing leading segments are omitted.
|
|
28
|
+
*/
|
|
29
|
+
function buildFqn(...segments) {
|
|
30
|
+
return segments.filter((segment) => segment !== void 0 && segment.length > 0).map((segment) => quoteIdentifier(segment)).join(".");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Quote a Snowflake string literal, doubling embedded single quotes.
|
|
34
|
+
*/
|
|
35
|
+
function quoteLiteral(value) {
|
|
36
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Compose `LIKE '<pattern>'` when `pattern` is provided, empty string
|
|
40
|
+
* otherwise. Pattern is escaped as a SQL literal.
|
|
41
|
+
*/
|
|
42
|
+
function likeClause(pattern) {
|
|
43
|
+
return pattern ? ` LIKE ${quoteLiteral(pattern)}` : "";
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Compose `IN <scopeKeyword> <quoted identifier>` clause (e.g. "IN DATABASE
|
|
47
|
+
* MYDB"). Caller supplies a pre-joined identifier (fqn already built).
|
|
48
|
+
*/
|
|
49
|
+
function inClause(scopeKeyword, target) {
|
|
50
|
+
if (!target) return "";
|
|
51
|
+
if (scopeKeyword === "ACCOUNT") return " IN ACCOUNT";
|
|
52
|
+
return ` IN ${scopeKeyword} ${target}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
export { quoteLiteral as a, quoteIdentifier as i, inClause as n, likeClause as r, buildFqn as t };
|
package/dist/sql.d.mts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { SnowflakeClient } from "./client.mjs";
|
|
2
|
+
import { a as SubmitStatementInput, d as HydratedRow, f as PartitionResponse, m as ColumnMeta, n as StatementResponse } from "./statements-DJL0qVNA.mjs";
|
|
3
|
+
//#region src/sql.d.ts
|
|
4
|
+
interface SubmitOptions {
|
|
5
|
+
readonly async?: boolean;
|
|
6
|
+
readonly requestId?: string;
|
|
7
|
+
}
|
|
8
|
+
interface SubmitResult {
|
|
9
|
+
readonly response: StatementResponse;
|
|
10
|
+
readonly status: 'done' | 'failed' | 'pending' | 'cancelled';
|
|
11
|
+
readonly httpStatus: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Submit a statement. When `async: true` (or the server chooses async) we
|
|
15
|
+
* return the inflated response envelope along with the `pending` status so
|
|
16
|
+
* callers can poll via {@link getStatement} / {@link waitForStatement}.
|
|
17
|
+
*
|
|
18
|
+
* The SQL API itself keys idempotency on the `requestId` query param which
|
|
19
|
+
* {@link SnowflakeClient} auto-populates — callers can override it per
|
|
20
|
+
* request when they need deterministic retries.
|
|
21
|
+
*/
|
|
22
|
+
declare function submitStatement(client: SnowflakeClient, input: SubmitStatementInput, options?: SubmitOptions): Promise<SubmitResult>;
|
|
23
|
+
/**
|
|
24
|
+
* Fetch the current status of a previously submitted async statement. Returns
|
|
25
|
+
* both the envelope and the derived `status` bucket.
|
|
26
|
+
*/
|
|
27
|
+
declare function getStatement(client: SnowflakeClient, handle: string, options?: {
|
|
28
|
+
readonly partition?: number;
|
|
29
|
+
}): Promise<SubmitResult>;
|
|
30
|
+
/**
|
|
31
|
+
* POST `/cancel`. Snowflake responds with `code: 333335` on success — we
|
|
32
|
+
* normalise anything else into a {@link SnowflakeApiError}.
|
|
33
|
+
*/
|
|
34
|
+
declare function cancelStatement(client: SnowflakeClient, handle: string): Promise<void>;
|
|
35
|
+
interface ExecuteSqlOptions extends SubmitOptions {
|
|
36
|
+
readonly pollIntervalMs?: number;
|
|
37
|
+
readonly maxWaitMs?: number;
|
|
38
|
+
readonly hydrate?: boolean;
|
|
39
|
+
readonly now?: () => number;
|
|
40
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
interface ExecuteSqlResult<TRow = HydratedRow> {
|
|
43
|
+
readonly handle: string;
|
|
44
|
+
readonly columns: readonly ColumnMeta[];
|
|
45
|
+
readonly rows: readonly TRow[];
|
|
46
|
+
readonly envelope: StatementResponse;
|
|
47
|
+
readonly partitions: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Submit + wait + fetch all partitions in one go. The returned rows are
|
|
51
|
+
* hydrated into objects keyed by column name by default; pass
|
|
52
|
+
* `hydrate: false` to get the raw tuple form.
|
|
53
|
+
*/
|
|
54
|
+
declare function executeSql(client: SnowflakeClient, input: SubmitStatementInput, options?: ExecuteSqlOptions): Promise<ExecuteSqlResult<HydratedRow | readonly unknown[]>>;
|
|
55
|
+
/**
|
|
56
|
+
* Composio `SNOWFLAKE_BASIC_RUN_QUERY` parity — requires an explicit
|
|
57
|
+
* database + schema to match the published BASIC shape.
|
|
58
|
+
*/
|
|
59
|
+
declare function runQuery(client: SnowflakeClient, input: {
|
|
60
|
+
readonly statement: string;
|
|
61
|
+
readonly database: string;
|
|
62
|
+
readonly schema_name: string;
|
|
63
|
+
readonly warehouse?: string;
|
|
64
|
+
readonly role?: string;
|
|
65
|
+
}, options?: ExecuteSqlOptions): Promise<ExecuteSqlResult<HydratedRow | readonly unknown[]>>;
|
|
66
|
+
/**
|
|
67
|
+
* Fetch a single partition for a known handle.
|
|
68
|
+
*/
|
|
69
|
+
declare function getStatementResultPartition(client: SnowflakeClient, handle: string, partition: number): Promise<PartitionResponse>;
|
|
70
|
+
/**
|
|
71
|
+
* Async iterator over every partition for a handle. Useful for streaming
|
|
72
|
+
* large result sets into downstream pipelines.
|
|
73
|
+
*/
|
|
74
|
+
declare function iterateStatementResult(client: SnowflakeClient, handle: string, options?: {
|
|
75
|
+
readonly maxPartitions?: number;
|
|
76
|
+
}): AsyncGenerator<PartitionResponse, void, void>;
|
|
77
|
+
/**
|
|
78
|
+
* Blocking helper: polls `getStatement` until the statement reaches a
|
|
79
|
+
* terminal status or the `maxWaitMs` budget elapses. Returns the terminal
|
|
80
|
+
* envelope (throws for `failed` / `cancelled`).
|
|
81
|
+
*/
|
|
82
|
+
declare function waitForStatement(client: SnowflakeClient, handle: string, options?: ExecuteSqlOptions): Promise<StatementResponse>;
|
|
83
|
+
//#endregion
|
|
84
|
+
export { ExecuteSqlOptions, ExecuteSqlResult, SubmitOptions, SubmitResult, cancelStatement, executeSql, getStatement, getStatementResultPartition, iterateStatementResult, runQuery, submitStatement, waitForStatement };
|