Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added wasm and created extract_table_names method #1

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 73 additions & 2 deletions Cargo.lock

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

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ homepage = "https://github.com/ClearTax/sqlcli"
license = "MIT"
edition = "2018"

[toolchain]
channel = "nightly"
[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
sqlparser = "0.13.0"
simple_logger = "1.16.0"
clap = "3.0.0-beta.2"
wasm-bindgen = "0.2.17"
js-sys = "0.3.56"
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# sqlcli

`sqlcli` is a CLI tool written to parse SQL statements and answer questions about them.
`sqlcli` is a CLI (and [WASM](#wasm)) tool written to parse SQL statements and answer questions about them.

Currently, it only really does two things:

Expand All @@ -11,7 +11,7 @@ Currently, it only really does two things:

It's written in rust. Probably not very idiomatic rust, since it was written in a hurry. Feedback welcome!

## Usage
## Command Line Usage

Display help:

Expand Down Expand Up @@ -145,7 +145,18 @@ WILDCARD: b.*
```


## Build & test


## WASM

Building for WASM:

```
# Install wasm-pack first (eg: `brew install wasm-pack`)
$ wasm-pack build --target nodejs
```

## Build and Test

Install [rust](https://www.rust-lang.org/), and run:

Expand Down
31 changes: 21 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#![feature(box_syntax, box_patterns)]
#![allow(clippy::clippy::needless_return)]
#![allow(clippy::needless_return)]

extern crate sqlparser;

Expand All @@ -8,14 +7,15 @@ use sqlparser::ast::*;
use sqlparser::dialect::GenericDialect;
use sqlparser::parser::Parser;
use std::collections::HashSet;
use wasm_bindgen::prelude::*;

pub fn parse_select_statement(sql: String) -> Result<Query, String> {
let dialect = GenericDialect {};
let results = Parser::parse_sql(&dialect, &sql);

match results {
Err(error) => {
return Err(format!("Parsing error: {}", error.to_string()));
return Err(format!("Parsing error: {}", error));
}
Ok(mut ast) => {
if ast.is_empty() {
Expand Down Expand Up @@ -128,12 +128,12 @@ fn table_names_from_set_expr(body: SetExpr) -> Vec<String> {
}

SetExpr::SetOperation {
box left,
box right,
left,
right,
..
} => {
let mut table_names = table_names_from_set_expr(left);
let l2 = table_names_from_set_expr(right);
let mut table_names = table_names_from_set_expr(*left);
let l2 = table_names_from_set_expr(*right);
table_names.extend(l2);
return table_names;
}
Expand Down Expand Up @@ -455,8 +455,8 @@ mod table_name_tests {
#[test]
fn case_with_sub_query() {
assert(
"select
abc,
"select
abc,
(case when user_id > 0 then (select max(active_time) from users where id = user_id) else NULL end) last_active_time
from events",
vec!["events", "users"]);
Expand All @@ -465,7 +465,7 @@ vec!["events", "users"]);
#[test]
fn window_function() {
assert(
"select emp_name, dealer_id, sales, avg(sales) over (partition by dealer_id) as avgsales from q1_sales;",
"select emp_name, dealer_id, sales, avg(sales) over (partition by dealer_id) as avgsales from q1_sales;",
vec!["q1_sales"]);
}
}
Expand Down Expand Up @@ -558,6 +558,17 @@ fn projection_name_from_expr(e: Expr) -> String {
}
}

#[wasm_bindgen]
pub fn extract_table_names(sql: String) -> JsValue {
Copy link
Author

@rameezshuhaibcleartax rameezshuhaibcleartax Mar 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Anks I'm not sure which is the best option here monadic error handling or raising alerts.

type Error = String;
pub fn extract_table_names(sql: String) -> (Error, JsValue)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, I have just made the function return a string (in case of error) or an array of strings in case of success. Not a great interface, but it allows the caller to distinguish between parsing failure and empty state

match get_table_names(sql) {
Ok(table_names) =>
JsValue::from(table_names.into_iter()
.map(|x| JsValue::from_str(x.as_str()))
.collect::<js_sys::Array>()),
Err(e) => JsValue::from_str(format!("Error in Query: {:?}", e).as_str())
}
}

#[cfg(test)]
mod projection_tests {

Expand Down
8 changes: 4 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#![allow(clippy::clippy::needless_return)]
#![allow(clippy::needless_return)]

use clap::App;
use clap::AppSettings;
Expand Down Expand Up @@ -81,7 +81,7 @@ fn main() {
SimpleLogger::new().with_utc_timestamps().init().unwrap();
}

if let Some(ref matches) = matches.subcommand_matches("ast") {
if let Some(matches) = matches.subcommand_matches("ast") {
let input = matches.value_of("<INPUT>").unwrap();
let sql = read_file_or_stdin(input.to_string());
match parse_select_statement(sql) {
Expand All @@ -95,7 +95,7 @@ fn main() {
}
}

if let Some(ref matches) = matches.subcommand_matches("tables") {
if let Some(matches) = matches.subcommand_matches("tables") {
let input = matches.value_of("<INPUT>").unwrap();
let sql = read_file_or_stdin(input.to_string());
let result = get_table_names(sql);
Expand All @@ -113,7 +113,7 @@ fn main() {
}
}

if let Some(ref matches) = matches.subcommand_matches("columns") {
if let Some(matches) = matches.subcommand_matches("columns") {
let input = matches.value_of("<INPUT>").unwrap();
let sql = read_file_or_stdin(input.to_string());
let result = get_projection_names(sql);
Expand Down