Skip to content

Commit

Permalink
inline tags
Browse files Browse the repository at this point in the history
  • Loading branch information
brugos committed Feb 28, 2021
1 parent fa7e76c commit 8f078eb
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 105 deletions.
112 changes: 57 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ A Multi SQL Statement, Promise-based and Typescript version to your [Snowflake](
Also adds nice features like:

- replaces **tags** defined in the scripts by name using original data types (examples below).
- Also replaces **inline tags** like `select * from {%table_name%}` so you can play around different scenarios between code and the sql script. Use cases: replace database name depending on the environment used (ex: db_name_dev, db_name_prod)
- Emits **progress** events so you can monitor long run calls
- **Preview** parsed statement before sending to Snowflake.
- Shows **duration** (in miliseconds) for each chunk as well the **totalDuration**.
Expand Down Expand Up @@ -48,58 +49,7 @@ The unique method's param is deconstructed into the variables below:

---

## Basic usage

```typescript
const Snowflake = require("snowflake-multisql").Snowflake;
// or, for TypeScript:
import { Snowflake } from "snowflake-multisql";

async function main() {
const snowflake = new Snowflake({
account: "<account name>",
username: "<username>",
password: "<password>",
database: "DEMO_DATABASE",
schema: "DEMO_SCHEMA",
warehouse: "DEMO_WH",
});

await snowflake.connect();

const sqlText = `
CREATE OR REPLACE TABLE temp_table_customer as
SELECT COUNT(*) FROM customer WHERE C_MKTSEGMENT=:1;
USE SCHEMA demo_schema;
SELECT COUNT(*) FROM customer WHERE C_MKTSEGMENT_ID=:2;
`;
const binds = ["AUTOMOBILE", 1234];

// NEW FEATURE: Feel free to monitor your running progress
snowflake.progress.on("news", (data) =>
console.log(`
progress: ${data.chunkOrder}/${data.chunksTotal}
duration: ${data.duration} ms,
totalDuration: ${data.totalDuration} ms
`)
);

const rows = await snowflake.executeAll({
sqlText,
binds,
includeResults: true,
});

console.log(rows);
}

main();
```

---

## Advanced options
## Usage

```typescript
import { Snowflake, loadFiles } from "snowflake-multisql"
Expand All @@ -123,20 +73,21 @@ main();

// file-1.sql content:
// CREATE OR REPLACE TABLE temp_table_customer as
// SELECT COUNT(*) FROM customer WHERE PURCHASE_DATE={%purchase_date%};
// SELECT COUNT(*) FROM {%table_name%} WHERE PURCHASE_DATE={%purchase_date%};

// file-2.sql content:
// SELECT * from temp_table_customer
// WHERE product_name = {%product_name%}

// file-3.sql content:
// USE SCHEMA demo_schema;
// SELECT COUNT(*) FROM customer WHERE segment_id={%segment_id%};
// SELECT COUNT(*) FROM {%table_name%} WHERE segment_id={%segment_id%};

const tags = [
{ tag: "purchase_date", value: new Date(1610976670682) },
{ tag: "product_name", value: "AUTOMOBILE" },
{ tag: "segment_id", value: 1234 },
{ tag: "table_name", value: "customers", inline: true },
];

// NEW FEATURE: Feel free to monitor your running progress
Expand All @@ -161,7 +112,7 @@ main();

---

## Using GENERIC TYPES
## Using GENERIC types

```typescript
import { Snowflake, loadFiles } from "snowflake-multisql"
Expand Down Expand Up @@ -197,3 +148,54 @@ main();

main();
```

---

## If you prefer to manage the original binds by yourself

```typescript
const Snowflake = require("snowflake-multisql").Snowflake;
// or, for TypeScript:
import { Snowflake } from "snowflake-multisql";

async function main() {
const snowflake = new Snowflake({
account: "<account name>",
username: "<username>",
password: "<password>",
database: "DEMO_DATABASE",
schema: "DEMO_SCHEMA",
warehouse: "DEMO_WH",
});

await snowflake.connect();

const sqlText = `
CREATE OR REPLACE TABLE temp_table_customer as
SELECT COUNT(*) FROM customer WHERE C_MKTSEGMENT=:1;
USE SCHEMA demo_schema;
SELECT COUNT(*) FROM customer WHERE C_MKTSEGMENT_ID=:2;
`;
const binds = ["AUTOMOBILE", 1234];

// NEW FEATURE: Feel free to monitor your running progress
snowflake.progress.on("news", (data) =>
console.log(`
progress: ${data.chunkOrder}/${data.chunksTotal}
duration: ${data.duration} ms,
totalDuration: ${data.totalDuration} ms
`)
);

const rows = await snowflake.executeAll({
sqlText,
binds,
includeResults: true,
});

console.log(rows);
}

main();
```
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "snowflake-multisql",
"version": "1.5.0",
"version": "1.6.0",
"description": "Multi SQL Statement, Promise-based, TypeScript wrapper for Snowflake SDK",
"repository": "github:brugos/snowflake-multisql",
"bugs": "https://github.com/brugos/snowflake-multisql/issues",
Expand Down
88 changes: 42 additions & 46 deletions src/snowflake-multisql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type IExecuteAllResult<T> = IExecuteAllPreview & {
export type ITag = {
tag: string;
value: any;
inline?: boolean;
};
export type IExecuteAll = {
sqlText: string;
Expand All @@ -32,9 +33,23 @@ export class SnowflakeMultiSql extends Snowflake {
super(conn);
}

//public replaceInlineTags(fileText: string, binds: ITag[]): string {
private replaceInlineTags(params: IExecuteAll): IExecuteAll {
let text: string = params.sqlText;
params.tags = params.tags || [];
const inlineTags = params.tags.filter((tag) => tag.inline);
inlineTags?.map((bind) => {
const regex = new RegExp("{%" + bind.tag + "%}", "gi");
params.sqlText = params.sqlText.replace(regex, bind.value);
});
params.tags = params.tags.filter((tag) => !tag.inline);
return params;
}

public async executeAll<T = Record<string, string>>(
params: IExecuteAll
): Promise<IExecuteAllResult<T>[]> {
this.replaceInlineTags(params);
const chunks = this.getChunks(params.sqlText);
const chunksTotal = chunks.length;
const results: IExecuteAllResult<T>[] = [];
Expand Down Expand Up @@ -75,37 +90,18 @@ export class SnowflakeMultiSql extends Snowflake {
return results;
}

/**
* Tag Replacement alternative
* replaces by name instead of sequence as per snowflake-sdk execute function
* @param fileText
* @param binds
*/
public replaceTags(fileText: string, binds: ITag[]): string {
let text: string = fileText;
binds?.map((bind) => {
const regex = new RegExp("{%" + bind.tag + "%}", "gi");
text = text.replace(regex, bind.value);
});
return text;
}

/**
* Transforms nominal tags replacement into sequence as expected by the original library
* @param sqlText
* @param binds
*/
public tagsToBinds(
sqlText: string,
tagValues: ITag[] = []
tags: ITag[] = []
): { sqlText: string; binds: any[] } {
try {
const allRegex: RegExp = /{%\w+%}/gim;
const ret = {
sqlText,
binds: [],
};
const rawTags = sqlText.match(allRegex) || [];
const ret = { sqlText, binds: [] };
const rawTags = sqlText.match(/{%\w+%}/gim) || [];
const sqlTextTags: {
raw?: string;
clean?: string;
Expand All @@ -114,36 +110,13 @@ export class SnowflakeMultiSql extends Snowflake {
clean: raw.replace("%}", "").replace("{%", ""),
}));

// sqlTextTags.clean = sqlTextTags.raw.map((t) => cleanTextTag(t));
// const cleanTextTag = (textTag) =>
// textTag.replace("%}", "").replace("{%", "");

const checkTags = (textTags: string[], tagValues: ITag[]) => {
const uniqueTextTags = textTags.filter(
(txtTag, i) => textTags.indexOf(txtTag) === i
);
// console.log(uniqueTextTags);
uniqueTextTags.map((uniqueTag) => {
if (!tagValues.find((tv) => tv.tag === uniqueTag))
throw new Error(
"###"
// `it seems you've forgotten to list the tag ${uniqueTag}`
);
});
};

checkTags(
sqlTextTags.map((tt) => tt.clean),
tagValues
);

let _i = 0;
sqlTextTags.forEach((textTag, _i) => {
ret.sqlText = ret.sqlText.replace(
textTag.raw,
":".concat(String(_i + 1))
);
const tagValue = tagValues?.find((obj) => obj.tag === textTag.clean);
const tagValue = tags?.find((obj) => obj.tag === textTag.clean);
if (tagValue) ret.binds.push(tagValue?.value);
});

Expand All @@ -153,6 +126,29 @@ export class SnowflakeMultiSql extends Snowflake {
}
}

private checkTagsExistence(
sqlTextTags: { raw?: string; clean?: string }[],
tagValues: ITag[]
) {
const checkTags = (textTags: string[], tagValues: ITag[]) => {
const uniqueTextTags = textTags.filter(
(txtTag, i) => textTags.indexOf(txtTag) === i
);
uniqueTextTags.map((uniqueTag) => {
if (!tagValues.find((tv) => tv.tag === uniqueTag))
throw new Error(
"###"
// `it seems you've forgotten to list the tag ${uniqueTag}`
);
});
};

checkTags(
sqlTextTags.map((tt) => tt.clean),
tagValues
);
}

/**
* splits in chunks due to inability of Snowflake driver
* to process multiple requests in a single call
Expand Down
21 changes: 18 additions & 3 deletions tests/snowflake-multisql.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ describe("checks tagsToBinds function", () => {
let snowflake: Snowflake;

const sqlText: string =
"select * from {%tag0%} {%tag2%} {%tag0%} {%tag4%} where {%tag2%} {%tag4%}";
"select * from {%inline1%} {%tag0%} {%tag2%} {%tag0%} {%tag4%} where {%tag2%} {%tag4%}";
const tags: ITag[] = [
{ tag: "tag0", value: "hi" },
{ tag: "tag2", value: 123 },
{ tag: "tag4", value: new Date(1610976670682) },
{ tag: "inline1", value: "tblName", inline: true },
];
type Conversion = {
ID: string;
Expand Down Expand Up @@ -72,9 +73,9 @@ describe("checks tagsToBinds function", () => {
expect(spyProgress.called).to.be.true;
});

it("matches expected response", async () => {
it("check binds functionality", async () => {
const expected = {
chunkText: "select * from :1 :2 :3 :4 where :5 :6",
chunkText: "select * from tblName :1 :2 :3 :4 where :5 :6",
chunkOrder: 1,
chunksTotal: 1,
binds: [
Expand All @@ -94,6 +95,20 @@ describe("checks tagsToBinds function", () => {
expect(ret[0]).to.containSubset(expected);
});

it("check tags absence", async () => {
const sqlText = "select * from tableName";
const expected = {
chunkText: sqlText,
chunkOrder: 1,
chunksTotal: 1,
binds: [],
};
const ret = await snowflake.executeAll({
sqlText,
});
expect(ret[0]).to.containSubset(expected);
});

it("no tags sqltext, no tags params", () => {
const sqlText = "select * from table";
const ret = snowflake.tagsToBinds(sqlText);
Expand Down

0 comments on commit 8f078eb

Please sign in to comment.