Skip to content

Commit

Permalink
Merge pull request #1 from nealwp/updates
Browse files Browse the repository at this point in the history
Updates
  • Loading branch information
nealwp authored Aug 28, 2023
2 parents 5dbce01 + 714804a commit 16bd3bf
Show file tree
Hide file tree
Showing 8 changed files with 197 additions and 38 deletions.
70 changes: 60 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,17 @@ This CLI tool reads a JSON file and produces BigQuery compatible SQL views from
## Usage

```bash
npx @nealwp/blobview <filepath>
npx @nealwp/blobview@latest [options] <filepath>
```

```text
Arguments:
filepath path to valid JSON file
Options:
-t TABLE, --table=TABLE specify a table name to use in FROM clause. default: "<table>"
-d DATASET, --dataset=DATASET specify a dataset to use in FROM clause. default: "<dataset>"
-h, --help show help
```

## Examples:
Expand All @@ -19,9 +29,16 @@ Default output to STDOUT:
npx @nealwp/blobview ./path/to/file.json
```

Dataset and table as input options:
```bash
npx @nealwp/blobview --dataset=myDataset --table=myTable ./path/to/file.json
# shorthand options
npx @nealwp/blobview -d myDataset -t myTable ./path/to/file.json
```

Redirect output to file:
```bash
npx @nealwp/blobview ./path/to/file.json > my-view-file.sql
npx @nealwp/blobview@latest ./path/to/file.json > my-view-file.sql
```

## Features
Expand All @@ -32,6 +49,7 @@ npx @nealwp/blobview ./path/to/file.json > my-view-file.sql
* Auto-formats column names to snake_case from camelCase and PascalCase
* Detects deeply-nested objects and formats to JSON string
* Pre-populates FROM clause with BigQuery-style placeholders
* BigQuery dataset and table name can be supplied as input options

## Limitations
* Does not detect DATE or TIMESTAMP types, or other types like BOOLEAN
Expand All @@ -40,7 +58,7 @@ npx @nealwp/blobview ./path/to/file.json > my-view-file.sql
* Does not create SQL views in any syntax other than BigQuery
* Requires a local JSON file to read
* Does not include option to write queries to separate files instead of STDOUT
* BigQuery project, dataset, and datastream names cannot be supplied as input
* BigQuery project name cannot be supplied as input

## Example Output

Expand Down Expand Up @@ -100,20 +118,52 @@ SELECT
, CAST(JSON_VALUE(json_blob.integerField) as INTEGER) as integer_field
, CAST(JSON_VALUE(json_blob.decimalField) as DECIMAL) as decimal_field
, TO_JSON_STRING(json_blob.exampleGeoJson) as example_geo_json
FROM <project>.<datastream>.<dataset>
--------
FROM <project>.<dataset>.<table>
/**/
SELECT
CAST(JSON_VALUE(json_blob.childField1.gender) as STRING) as gender
, CAST(JSON_VALUE(json_blob.childField1.latitude) as DECIMAL) as latitude
FROM <project>.<dataset>.<table>
/**/
SELECT
CAST(JSON_VALUE(json_blob.childField2.favoriteFruit) as STRING) as favorite_fruit
, CAST(JSON_VALUE(json_blob.childField2.longitude) as DECIMAL) as longitude
FROM <project>.<dataset>.<table>
/**/
SELECT
CAST(JSON_VALUE(json_blob.childWithNestedObject.isNormal) as STRING) as is_normal
, TO_JSON_STRING(json_blob.childWithNestedObject.nestedObject) as nested_object
FROM <project>.<dataset>.<table>
```

```bash
# terminal command with input options
npx @nealwp/blobview --dataset=myDataset --table=myTable sample-data.json
```

Will produce the following output:

```sql
/* stdout */
SELECT
CAST(JSON_VALUE(json_blob.stringField) as STRING) as string_field
, CAST(JSON_VALUE(json_blob.integerField) as INTEGER) as integer_field
, CAST(JSON_VALUE(json_blob.decimalField) as DECIMAL) as decimal_field
, TO_JSON_STRING(json_blob.exampleGeoJson) as example_geo_json
FROM <project>.myDataset.myTable
/**/
SELECT
CAST(JSON_VALUE(json_blob.childField1.gender) as STRING) as gender
, CAST(JSON_VALUE(json_blob.childField1.latitude) as DECIMAL) as latitude
FROM <project>.<datastream>.<dataset>
--------
FROM <project>.myDataset.myTable
/**/
SELECT
CAST(JSON_VALUE(json_blob.childField2.favoriteFruit) as STRING) as favorite_fruit
, CAST(JSON_VALUE(json_blob.childField2.longitude) as DECIMAL) as longitude
FROM <project>.<datastream>.<dataset>
--------
FROM <project>.myDataset.myTable
/**/
SELECT
CAST(JSON_VALUE(json_blob.childWithNestedObject.isNormal) as STRING) as is_normal
, TO_JSON_STRING(json_blob.childWithNestedObject.nestedObject) as nested_object
FROM <project>.<datastream>.<dataset>
FROM <project>.myDataset.myTable
```
20 changes: 17 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nealwp/blobview",
"version": "0.1.0",
"version": "0.2.0",
"description": "Generate BigQuery SQL views from JSON",
"bin": {
"@nealwp/blobview": "./index.js"
Expand All @@ -23,6 +23,7 @@
"license": "ISC",
"devDependencies": {
"@types/jest": "^29.5.0",
"@types/minimist": "^1.2.2",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1",
"copyfiles": "^2.4.1",
Expand All @@ -36,5 +37,8 @@
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"typescript": "^5.0.3"
},
"dependencies": {
"minimist": "^1.2.8"
}
}
3 changes: 2 additions & 1 deletion src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ describe('main', () => {
it('should log error if no filepath is passed', () => {
process.argv = ['foo', 'bar']
console.log = jest.fn()
main()
const args = {_: []}
main(args)
expect(console.log).toHaveBeenCalled()
})
})
58 changes: 50 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,72 @@
#!/usr/bin/env node

import minimist from 'minimist'
import { readJsonFileContent, writeToStdOut } from "./io"
import { jsonToSqlView } from "./parser"

export const main = () => {
const args = process.argv.slice(2)
const inputFilePath = args[0]
export const main = (args: minimist.ParsedArgs) => {
const inputFilePath = args._[0]

let table = '<table>'
let dataset = '<dataset>'

if(args.help || args.h) {
printHelp()
return
}

if(!inputFilePath) {
printHelp();
printError("No file path provided!");
return
}

if (args.table) {
table = args.table
} else if (args.t) {
table = args.t
}

if (args.dataset) {
dataset = args.dataset
} else if (args.d) {
dataset = args.d
}

try {
const fileContent = readJsonFileContent(inputFilePath)
const sqlOutput = jsonToSqlView(fileContent)
const sqlOutput = jsonToSqlView(fileContent, {table, dataset})
writeToStdOut(sqlOutput)
} catch(err) {
console.log(err)
}
}

function printHelp() {
const msg = `Error: no file path provided.\nUsage:\n\nnpx @nealwp/blobview <filepath>\n`
console.log(msg)
const helpText = `Generate BigQuery SQL views from JSON.
Usage:
@nealwp/blobview [options] <filepath>
Arguments:
filepath path to valid JSON file
Options:
-t TABLE, --table=TABLE specify a table name to use in FROM clause. default: "<table>"
-d DATASET, --dataset=DATASET specify a dataset to use in FROM clause. default: "<dataset>"
-h, --help show help`

console.log(helpText)
}

main();
function printError(message: string) {
console.log(`Error: ${message}`)
console.log('Run "@nealwp/blobview --help" to display help.')
}

const args = minimist(process.argv.slice(2), {
stopEarly: true,
boolean: false
})

main(args);

2 changes: 1 addition & 1 deletion src/io.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function readJsonFileContent(path: string) {
export function writeToStdOut(data: {parentSql: string, childQueries: string[]}) {
console.log(data.parentSql)
data.childQueries.forEach(q => {
console.log("--------")
console.log("/**/")
console.log(q)
})
}
Loading

0 comments on commit 16bd3bf

Please sign in to comment.