Skip to content
This repository has been archived by the owner on Aug 15, 2024. It is now read-only.

Commit

Permalink
Merge pull request #9 from factorialco/adding-repl
Browse files Browse the repository at this point in the history
Adding an interactive repl to run queries
  • Loading branch information
gtrias authored Aug 12, 2021
2 parents e0ee96d + 22e657f commit 18d18ce
Show file tree
Hide file tree
Showing 12 changed files with 397 additions and 85 deletions.
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

Query your HTTP endpoints data using SQL

## Install

### Using docker

`docker run -p 8080 -ti factorialco/tentaclesql`

### As system command

```bash
npm i -g @factorialco/tentaclesql
```

## Setup

To be able to start TentacleSQL you need to provide a `SCHEMA_URL` environment
Expand Down Expand Up @@ -84,7 +96,7 @@ to denote relations between tables.
- `table`: Target table of the foreign key
- `foreign_key`: Referenced column by the foreign key

## Usage
## Usage HTTP API

Once you have your tentaclesql server up and running you can use it by sending
POST requests against `/`.
Expand All @@ -104,3 +116,21 @@ The expected payload contains the following parameters:
https://github.com/nalgeon/sqlean to see all the supported extensions and how
to use them.
- **schema**: Manual schema definition

## Usage CLI

### Run interactive prompt

```bash
yarn cli interactive
# or installed global
tentaclesql interactive
```

### Execute queries

```bash
yarn cli query "SELECT 1;"
# or installed global
tentaclesql query "SELECT 1;"
```
43 changes: 28 additions & 15 deletions cli.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,33 @@
import yargs from 'yargs/yargs'
#!/usr/bin/env node

import { Command } from 'commander'
import executor from './src/executor'
import startRpl from './src/repl'

const program = new Command()

const argv = yargs(process.argv.slice(2)).options({
query: { type: 'string', demandOption: true }
}).argv
program
.command('query')
.argument('<sql>')
.action((sql: string) => {
const headers = { Cookie: `${process.env.COOKIE}` }

if (argv.query) {
const headers = { Cookie: `${process.env.COOKIE}` }
executor(
sql,
[],
headers
).then(result => {
console.log(result)
}).catch(error => {
console.log(error)
})
})

executor(
argv.query,
[],
headers
).then(result => {
console.log(result)
}).catch(error => {
console.log(error)
program
.command('interactive')
.action(() => {
console.log('interactive mode')
startRpl()
})
}

program.parse()
33 changes: 27 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
{
"name": "tentaclesql",
"name": "@factorialco/tentaclesql",
"version": "0.2.3",
"description": "SQL engine from multiple sources",
"bin": {
"tentaclesql": "dist/cli.js"
},
"main": "dist/src/executor/index.js",
"dependencies": {
"better-sqlite3": "^7.1.5",
"cli-table": "^0.3.6",
"commander": "^8.1.0",
"dd-trace": "^1.1.2",
"fastify": "^3.14.2",
"node-fetch": "^2.6.1",
"pino": "^6.11.3",
"pino-pretty": "^4.7.1",
"sqlite-parser": "^1.0.1",
"yargs": "^16.2.0"
"vorpal": "^1.12.0"
},
"devDependencies": {
"@babel/core": "^7.13.16",
"@babel/preset-env": "^7.13.15",
"@babel/preset-typescript": "^7.13.0",
"@types/better-sqlite3": "^5.4.1",
"@types/cli-table": "^0.3.0",
"@types/jest": "^26.0.23",
"@types/node": "^14.14.41",
"@types/node-fetch": "^2.5.10",
"@types/yargs": "^16.0.1",
"@types/pino": "^6.3.11",
"@types/vorpal": "^1.12.2",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"eslint": "^7.25.0",
Expand All @@ -36,14 +44,27 @@
"typescript": "^4.2.4"
},
"scripts": {
"build": "rimraf dist && tsup server.ts --format cjs,esm --dts",
"build": "rimraf dist && tsup server.ts src/executor/index.ts cli.ts --format cjs,esm --dts",
"cli": "LOG_LEVEL='debug' esno cli.ts",
"lint": "eslint . --ext .ts",
"start": "esno server.ts",
"test": "jest",
"test:watch": "jest --watch",
"watch": "yarn build -- --watch"
},
"author": "",
"license": "ISC"
"author": "Factorial <[email protected]>",
"license": "ISC",
"repository": {
"type": "git",
"url": "git+https://github.com/factorialco/tentaclesql.git"
},
"keywords": [
"sqlite",
"rest-api",
"cli"
],
"bugs": {
"url": "https://github.com/factorialco/tentaclesql/issues"
},
"homepage": "https://github.com/factorialco/tentaclesql#readme"
}
4 changes: 2 additions & 2 deletions src/executor/database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { Database as DatabaseType } from 'better-sqlite3'

type Extension = string

const EXTENSIONS = {
const EXTENSIONS: any = {
crypto: 'crypto',
json1: 'json1',
math: 'math',
Expand All @@ -23,7 +23,7 @@ function loadExtensions (
// https://github.com/nalgeon/sqlean
const extensionBase = path.join(__dirname, '/sqlite_extensions/')

extensions.forEach(extension => {
extensions.forEach((extension: string) => {
if (!EXTENSIONS[extension]) {
throw Error(`${extension} extension not found!`)
}
Expand Down
15 changes: 10 additions & 5 deletions src/executor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,14 @@ function getHost (): string {
return (new URL(getSchemaUrl())).hostname
}

function mutateDataframe (df, fn) {
function mutateDataframe (
df: Array<any>,
fn: (row: any, k: any) => void
) {
return df.forEach(row => { Object.keys(row).forEach(k => fn(row, k)) })
}

async function fetchTableData (tableDefinition: TableDefinition, headers) {
async function fetchTableData (tableDefinition: TableDefinition, headers: any) {
const res = await fetch(tableDefinition.url, { headers })

if (!res.ok) {
Expand All @@ -52,9 +55,11 @@ async function populateTables (
headers: any,
schema: any
) {
const filteredTableDefinition = schema.filter(tableDefinition => usedTables.includes(tableDefinition.name))
const filteredTableDefinition = schema.filter((
tableDefinition: TableDefinition
) => usedTables.includes(tableDefinition.name))

const promises = filteredTableDefinition.map(async (tableDefinition) => {
const promises = filteredTableDefinition.map(async (tableDefinition: TableDefinition) => {
createTable(db, tableDefinition)

const data = await fetchTableData(tableDefinition, headers)
Expand All @@ -72,7 +77,7 @@ async function populateTables (
return Promise.all(promises)
}

function storeToDb (db: DatabaseType, tableDefinition: TableDefinition, data) {
function storeToDb (db: DatabaseType, tableDefinition: TableDefinition, data: Array<any>) {
const schema = tableDefinition.fields.map(field => `@${field.key}`).join(', ')
const insert = db.prepare(`INSERT INTO ${tableDefinition.name} VALUES (${schema})`)
for (const row of data) insert.run(row)
Expand Down
2 changes: 1 addition & 1 deletion src/executor/queryParser/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { extractTables } from './index'

test('extractTables', () => {
const subject = (sql) => {
const subject = (sql: string) => {
return extractTables(sql)
}

Expand Down
2 changes: 1 addition & 1 deletion src/executor/queryParser/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import sqliteParser from 'sqlite-parser'

export function traverse (o: any, fn: (obj: any, prop: string, value: any) => void) {
const traverse = (o: any, fn: (obj: any, prop: string, value: any) => void) => {
for (const i in o) {
fn.apply(this, [o, i, o[i]])
if (o[i] !== null && typeof (o[i]) === 'object') {
Expand Down
2 changes: 1 addition & 1 deletion src/executor/schema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export async function fetchSchema (
return res.json()
}

const TYPES = { // Perdona Pau ;)
const TYPES: any = { // Perdona Pau ;)
bigint: 'BIGINT',
date: 'DATE',
number: 'INTEGER',
Expand Down
32 changes: 32 additions & 0 deletions src/repl/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Vorpal from 'vorpal'
import Table from 'cli-table'

import executor from '../executor'

const startRpl = () => {
const vorpal = new Vorpal()
vorpal
.command('sql <query>', 'Executes arbitrary sql.')
.action(async (args: any) => {
console.log('query args', args)
const results = await executor(args.query, [], {})

console.log('results: ', results)

const table = new Table({
head: Object.values(results[0])
})

results.forEach((result) => {
table.push(Object.values(result))
})

table.toString()
})

vorpal
.delimiter('tentaclesql >')
.show()
}

export default startRpl
5 changes: 3 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
"lib": ["esnext", "dom"],
"moduleResolution": "node",
"esModuleInterop": true,
"strict": false, // TODO: Move to strict true when migrated
"strict": true,
"strictNullChecks": true,
"resolveJsonModule": true
"resolveJsonModule": true,
"typeRoots": ["./types", "./node_modules/@types"]
}
}
1 change: 1 addition & 0 deletions types/sqlite-parser/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module 'sqlite-parser';
Loading

0 comments on commit 18d18ce

Please sign in to comment.