├── .github ├── FUNDING.yml └── workflows │ └── rust.yml ├── .gitignore ├── .gitlab-ci.yml ├── .travis.yml ├── Cargo.toml ├── Changelog.md ├── LICENSE ├── README.md ├── TODO.md ├── crates └── restq-http │ ├── .gitignore │ ├── Cargo.toml │ └── src │ └── lib.rs ├── examples └── simple.rs ├── justfile ├── rustfmt.toml ├── src ├── csv_rows.rs ├── data_pane.rs ├── data_type.rs ├── data_value.rs ├── lib.rs ├── multi_stmt.rs ├── plain_data.rs ├── statement.rs ├── statement │ ├── column_def.rs │ ├── display.rs │ ├── expr.rs │ ├── into_sql.rs │ ├── operator.rs │ ├── parser.rs │ ├── parser │ │ ├── tests.rs │ │ └── utils.rs │ ├── table.rs │ ├── table_def.rs │ └── value.rs ├── stmt_data.rs └── stmt_data │ └── parser.rs ├── test.sh └── tests ├── left_join.rs ├── select.rs └── test_table_def.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: ivanceras 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | 13 | - name: Build no features 14 | run: cargo build --all 15 | 16 | - name: Run tests 17 | run: cargo test --all --all-features 18 | 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | #Generated by rustorm 7 | #/examples/gen/** 8 | /gen/** 9 | # Executables 10 | *.exe 11 | 12 | # Generated by Cargo 13 | /target*/ 14 | 15 | #generated by eclipse 16 | /.project 17 | /.DS_Store 18 | 19 | #swap files 20 | *~ 21 | *.swp 22 | *.swo 23 | *.bk 24 | Cargo.lock 25 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - build 3 | 4 | rust-nightly: 5 | stage: build 6 | image: rustlang/rust:nightly 7 | before_script: 8 | - cd ../ && git clone https://github.com/apache/arrow && cd - 9 | - cd ../ && git clone -b alter-add-column https://github.com/ivanceras/sqlparser-rs && cd - 10 | - cd ../ && git clone https://github.com/ivanceras/pom && cd - 11 | script: 12 | - cargo build --all 13 | - cargo test --all --all-features 14 | 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - nightly 4 | 5 | script: 6 | - cargo build 7 | - cargo test 8 | 9 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "restq" 3 | version = "0.9.1" 4 | authors = [ "Jovansonlee Cesar" ] 5 | license = "MIT" 6 | description = "Compacting SQL query into a URL suitable for rest api calls" 7 | readme = "README.md" 8 | repository = "https://github.com/ivanceras/restq" 9 | documentation = "https://docs.rs/restq" 10 | edition = "2021" 11 | keywords = ["url", "param", "parser", "rest", "sql"] 12 | 13 | [dependencies] 14 | pom = { version = "3" } 15 | chrono = { version = "0.4", features = ["serde", "wasmbind"] } 16 | uuid = { version = "1.8", features = ["serde", "v4", "js"] } 17 | sqlparser = "0.44" 18 | thiserror = "1" 19 | csv = "1" 20 | log = "0.4" 21 | base64 = "0.22" 22 | either = "1.10" 23 | serde = { version = "1", features = ["derive"] } 24 | 25 | 26 | 27 | [workspace] 28 | members = [ 29 | "crates/restq-http", 30 | ] 31 | 32 | #[patch.crates-io] 33 | #sql-ast = { git = "https://github.com/ivanceras/sql-ast.git", branch = "master" } 34 | #sql-ast = { path = "../sql-ast" } 35 | 36 | 37 | [badges] 38 | maintenance = { status = "passively-maintained" } 39 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | # 0.7.0 4 | - Remove local from datatype and datavalue 5 | - update base64, uuid, to their latest version 6 | 7 | # 0.6.1 8 | - Remove the dependency to `js-sys` since we can use `Utc::now` from `chrono` using the `wasmbind` feature. 9 | 10 | # 0.6.0 11 | - Upgrade base64 0.8 -> 0.13 12 | - **breaking** Now uses base64::URL_SAFE for encoding/decoding bytes value. 13 | - move list_fail into utils 14 | - **breaking** Remove the alternate syntax for columns projection using parenthesis, instead just use braces 15 | - **breaking** Remove the alternate syntax for join type using caret for the arrow 16 | - Add multi_values parser for expression 17 | 18 | # 0.5.0 19 | - Rename `Table` to `TableName` and `Column` to `ColumnName` to signify that it only contains the name and not their definition 20 | - Improve multi-statement to ignore multiple whitespace and empty new lines 21 | 22 | # 0.4.0 23 | - Include the serial type in conversion to primitive types 24 | - Add support for multi_statement restq 25 | - modify the serial type to use custom serial type in the generated create sql statement 26 | - expose parse_header from stmt_data parser 27 | - Implement all variants of Statement 28 | - add helper function for column_def determing the generated default property 29 | - Implement creating a date now() in wasm using js date interface 30 | - Fix edge case conversion of dates when it is a blank string 31 | - Modify DataTypeDef default value to accomodate also Function as default is not limited to DataValue, it can also be functions 32 | - Add Implement Default for Direction 33 | - parse bulk_update 34 | - Add into_data_values, casting the simple value into a more specific data_type 35 | - Remove the column_def in csv_rows, plain_data and stmt_data since casting is not needed 36 | - Use the simple Value in CsvRows and StmtData, so as not to keep casting the values to and fro in the higher level usage 37 | - Implement conversion of BulkUpdate 38 | - Implement bulk update 39 | - implement conversion of Ilike operator 40 | 41 | ## 0.3.3 42 | - Add helper methods to Select AST 43 | - Implement fmt::Display for Select AST 44 | - Improve implementation of data conversion 45 | - Fix conversion of serial types 46 | 47 | ## 0.3.2 48 | - Fix issue: query only has table and paging, the paging was incorrectly parsed as filter. 49 | - Add parsing for common date format 50 | - reexport uuid 51 | - implement `fmt::Display` for TableDef, DataValue, Table, ColumnDef, Column, DataTypeDef etc ColumnAttribte 52 | - Add Bytes and Json DataType 53 | - expose PlainData in the crate 54 | 55 | ## 0.3.1 56 | - Add an expose `parse_select_chars` for usage in `inquerest` crate 57 | 58 | ## 0.3.0 59 | - Rename `CsvData` to a more appropriate name `StmtData` 60 | - Add PlainData which parses only data with table definition and the csv rows 61 | - modularize `csv_rows` into its own module 62 | 63 | ## 0.2.1 64 | - Add parser for bulk update, which is just the same with bulk delete 65 | - Remove complex string types such as email, domain, ipaddr since they can be stored as string 66 | - Modularize functions and expose the a function to extract only the restq from a request 67 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Jovansonlee Cesar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RestQ 2 | 3 | [![Latest Version](https://img.shields.io/crates/v/restq.svg)](https://crates.io/crates/restq) 4 | 5 | The simplest way to express data operations in a rest API. 6 | 7 | Implemented using the combination 8 | of appropriate HTTP methods, url and csv for the data format. 9 | 10 | 11 | Example: 12 | Querying a simple table `person` with filtering, grouping and paging. 13 | 14 | ``` 15 | GET /person?age=lt.42&(student=eq.true|gender=eq.'M')&group_by=sum(age),grade,gender&having=min(age)=gt.42&order_by=age.desc,height.asc&page=20&page_size=100 16 | ``` 17 | 18 | This can then be converted into a SQL query. 19 | ```sql 20 | SELECT * FROM person 21 | WHERE age < 42 22 | AND (student = true OR gender = 'M') 23 | GROUP BY sum(age), grade, gender 24 | HAVING min(age) > 42 25 | ORDER BY age DESC, height ASC 26 | LIMIT 100 OFFSET 1900 ROWS 27 | ``` 28 | The response body will contain a `csv` formatted data of the results from the query. 29 | 30 | 31 | **RestQ Syntax/Grammar:** 32 | ``` 33 | create = ["CREATE" | "PUT"], "/", table, column_def_list, "\n", csv 34 | 35 | select = "GET", "/", table, [join_table], column_list, [ "?", condition] 36 | 37 | delete = "DELETE", table, [ "?", condition ] 38 | 39 | update = ["UPDATE | "PATCH"] table, set_expr_list, [ "?", condition] 40 | 41 | drop = ["DROP" | "DELETE"] "-", table 42 | 43 | alter = ["ALTER" | "PATCH"] table, { drop_column | add_column | alter_column } 44 | 45 | drop_column = "-", column 46 | 47 | add_column = "+", column_def 48 | 49 | alter_column = column, "=", column_def 50 | 51 | 52 | column_def_list = "{", { column_def }, "}" 53 | | "(", { column_def }, ")" 54 | 55 | column_def = [ { column_attributes } ], column, [ "(" foreign ")" ], ":", data_type, [ "(" default_value ")" ] 56 | 57 | column_attributes = primary | index | unique 58 | 59 | primary = "*" 60 | 61 | index = "@" 62 | 63 | unique = "&" 64 | 65 | data_type = "bool" | "s8" | "s16" | "s32" | "s64" | "u8" | "u16", etc 66 | 67 | default_value = value 68 | 69 | value = number | string | bool ,..etc 70 | 71 | column = string, ".", string 72 | | string 73 | 74 | table = string 75 | 76 | foreign = table 77 | 78 | insert = table, column_list ,"\n", csv 79 | 80 | column_list = "{", { column }, "}" 81 | 82 | 83 | join_table = table, join_type, table 84 | 85 | join_type = right_join | left_join | inner_join | full_join 86 | 87 | right_join = "->" 88 | 89 | left_join = "<-" 90 | 91 | inner_join = "-><-" 92 | 93 | full_join = "<-->" 94 | 95 | condition = expr 96 | 97 | expr = column | value | binary_operation 98 | 99 | binary_operation = expr, operator, expr 100 | 101 | operator = "and" | "or" | "eq" | "gte" | "lte" ,..etc 102 | ``` 103 | 104 | 105 | ## Data types 106 | - `bool` : boolean 107 | - `s8` : u8 that autoincrements 108 | - `s16` : u16 that autoincrements 109 | - `s32` : u32 that autoincrements, serial 110 | - `s64` : u64 that autoincrements, bigserial 111 | - `f32` : float 4 bytes 112 | - `f64` : float 8 bytes 113 | - `i8`,`i16`,`i32`,`i64` : signed integer 114 | - `u8`,`u16`,`u32`,`u64` : unsigned intergers 115 | - `text` : utf8 string 116 | - `uuid` : plain uuid, randomly generated when null 117 | - `uuid_rand` : randomly generated uuid 118 | - `uuid_slug` : create a new uuid and generate a url friend base64 string out of it. 119 | - `utc` : timestamp with time zone in utc, 120 | - `url` : url types 121 | - `json` : json 122 | - `bytes` : binary data 123 | 124 | ## Creating a table and inserting records in one request. 125 | ``` 126 | PUT /+product{*product_id:s32,name:text,created_by(users):u32,created:utc,is_active:bool} 127 | Content-Type: text/csv; charset=UTF-8 128 | 129 | 1,go pro,1,2019-10-31 11:59:59.872,,true 130 | 2,shovel,1,2019-11-01 07:30:00.462,,false 131 | ``` 132 | 133 | - http method is `PUT` 134 | - url is a `restq` syntax. 135 | - body is `csv` 136 | 137 | **The equivalent SQL:** 138 | ```sql 139 | CREATE TABLE product ( 140 | product_id serial NOT NULL PRIMARY, 141 | name character varying NOT NULL, 142 | created_by integer NOT NULL REFERENCES users(user_id), 143 | created timestamp with time zone NOT NULL DEFAULT now(), 144 | is_active boolean NOT NULL 145 | 146 | INSERT INTO product(product_id, name, created_by, is_active) 147 | VALUES( 148 | (1,'go pro',1,2019-10-31 11:59:59.872,DEFAULT,true) 149 | (2,'shovel',1,2019-11-01 07:30:00.462,DEFAULT,false) 150 | ); 151 | ``` 152 | 153 | ## Show the table definition 154 | ``` 155 | HEAD /product 156 | 157 | ``` 158 | 159 | ## Show all tables 160 | ``` 161 | HEAD / 162 | ``` 163 | 164 | ## Querying the records 165 | 166 | ``` 167 | GET /product{product_id,name}?is_active=eq.true&order_by=created.desc 168 | ``` 169 | 170 | ```sql 171 | SELECT product_id,name FROM product WHERE is_active = true ORDER BY created DESC 172 | ``` 173 | 174 | ## Inserting records 175 | ``` 176 | POST /product{product_id,name,created_by,created,is_active} 177 | 1,go pro,1,2019-10-31 11:59:59.872,,true 178 | 2,shovel,1,2019-11-01 07:30:00.462,,false 179 | ``` 180 | 181 | ```sql 182 | INSERT INTO product(product_id, name, created_by, is_active) 183 | VALUES( 184 | (1,'go pro',1,2019-10-31 11:59:59.872,true) 185 | (2,'shovel',1,2019-11-01 07:30:00.462,false) 186 | ); 187 | ``` 188 | 189 | ## Insert with query 190 | ``` 191 | POST /user{user_id,name,person_id(GET/person{id}?person.name=name)} 192 | 1,TOM JONES,, 193 | ``` 194 | ```sql 195 | INSERT INTO user(user_id, name, person_id) 196 | VALUES(1, 'TOM JONES', (SELECT person.id FROM person WHERE person.name='TOM JONES')); 197 | ``` 198 | 199 | ## Updating records 200 | 201 | ``` 202 | PATCH /product{description="I'm the new description now"}?product_id=1 203 | ``` 204 | ```sql 205 | UPDATE product SET description = 'I\'m the new description now' WHERE product_id = 1; 206 | ``` 207 | 208 | ## Bulk updating records 209 | 210 | 2 versions of the same record is passed, first is the original, the next is the updated one 211 | ``` 212 | PATCH /product{*product_id,name} 213 | 1,go pro,1,go pro hero4 214 | 2,shovel,2,slightly used shovel 215 | ``` 216 | ```sql 217 | UPDATE product SET name = 'go pro hero4' WHERE id = 1; 218 | UPDATE product SET name = 'slightly used shovel' WHERE id = 2' 219 | ``` 220 | 221 | ## Delete 222 | 223 | ``` 224 | DELETE /product?product_id=1 225 | ``` 226 | 227 | ```sql 228 | DELETE FROM product WHERE product_id = '1' 229 | ``` 230 | 231 | ## Delete multiple 232 | ``` 233 | DELETE /product{product_id} 234 | 1 235 | 2 236 | 3 237 | ``` 238 | 239 | ```sql 240 | DELETE FROM product WHERE product_id IN ('1','2','3') 241 | ``` 242 | 243 | ## Delete multiple, by name(no primary keys). 244 | ``` 245 | DELETE /product{name,is_active} 246 | Go Pro,true 247 | Shovel,true 248 | Chair,true 249 | ``` 250 | ```sql 251 | DELETE FROM product WHERE name = 'Go Pro' AND is_active = 'true'; 252 | DELETE FROM product WHERE name = 'Shovel' AND is_active = 'true'; 253 | DELETE FROM product WHERE name = 'Chair' AND is_active = 'true'; 254 | ``` 255 | 256 | ## Delete all records of a table 257 | ``` 258 | DELETE /product 259 | ``` 260 | 261 | ```sql 262 | TRUNCATE product; 263 | ``` 264 | 265 | ## Complex select (with joins) 266 | 267 | ```restq 268 | GET /product<-users{product.*,users.user_name}?product_id=1&is_active=true&created=gt.2019-11-05T08:45:03.432 269 | ``` 270 | 271 | ```sql 272 | SELECT product.*, users.user_name 273 | FROM product 274 | LEFT JOIN users ON product.created_by = users.user_id 275 | WHERE product_id = 1 276 | AND is_active = true 277 | AND created > '2019-11-05T08:45:03.432' 278 | ``` 279 | 280 | 281 | ## Join tables 282 | 283 | ### Supported join types 284 | - [X] INNER JOIN `table1-><-table2` 285 | - OUTER JOIN 286 | - [X] LEFT JOIN `table1<-table2` 287 | - [X] RIGHT JOIN `table1->table2` 288 | - [X] FULL JOIN `table1<-->table2` 289 | 290 | ## Prior crate and inspiration 291 | - [inquerest](https://github.com/ivanceras/inquerest), in the works of porting to call this library. 292 | - [postgrest](https://github.com/PostgREST/postgrest), restq differs syntax to postgrest, with focus on intuitive filter clause 293 | 294 | #### Please support this project: 295 | [![Become a patron](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/ivanceras) 296 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | - [X] Convert restq to SQL 3 | - [ ] with the records as arguments to a prepared statement (parameterized query) 4 | - [ ] Blocked by, sqlparser don't support parameterize query yet. 5 | - [ ] Implement Update statement parser 6 | - [X] Implement Drop statement parser 7 | - [ ] Implement Alter statment parser 8 | - - [X] Implement drop_column parser 9 | - - [X] Implement add_column parser 10 | - - [X] Implement rename column parser 11 | - [ ] Support for money type using Bigdecimal crate 12 | - [X] Publish to crate.io 13 | ## Blocked by: 14 | - [X] sqlparser-rs (Problem: slow release cycle, busy main dev) 15 | - [X] -> Solution: release a fork crate name: sql-ast 16 | - [X] pom (expose method field in Parser) 17 | - [X] -> Solution: release a fork 18 | - [ ] Implement `fmt::Display` on `Statement` AST such as `Select` 19 | - [X] Rename `Table` to `TableName` and `Column` to `ColumnName` 20 | - [X] Remove the user-friendly parsing of join such as `^-` and just use the arrows `<-` 21 | The strict version works find in the url and just use `percent_decode` to get the original url text 22 | - [X] Remove the parenthesis grouping of columns and just use braces. 23 | - [ ] Implement conversion from sql statement to restq statement 24 | - [ ] SQL -> sql-ast -> restq-ast -> restq(url) 25 | - [ ] build the sql parser into restq using the simpler restq-ast 26 | - [ ] use prql ast 27 | 28 | -------------------------------------------------------------------------------- /crates/restq-http/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | #Generated by rustorm 7 | #/examples/gen/** 8 | /gen/** 9 | # Executables 10 | *.exe 11 | 12 | # Generated by Cargo 13 | /target*/ 14 | 15 | #generated by eclipse 16 | /.project 17 | /.DS_Store 18 | 19 | #swap files 20 | *~ 21 | *.swp 22 | *.swo 23 | *.bk 24 | Cargo.lock 25 | -------------------------------------------------------------------------------- /crates/restq-http/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "restq-http" 3 | version = "0.9.0" 4 | authors = ["Jovansonlee Cesar "] 5 | edition = "2018" 6 | readme = "../../README.md" 7 | license = "MIT" 8 | description = "Parse restq syntax from http crate" 9 | repository = "https://github.com/ivanceras/restq" 10 | documentation = "https://docs.rs/restq" 11 | keywords = ["http", "request","query", "sql"] 12 | 13 | 14 | [dependencies] 15 | http = "1.1" 16 | restq = { version = "0.9.0", path = "../../" } 17 | thiserror = "1" 18 | percent-encoding = "2" 19 | -------------------------------------------------------------------------------- /crates/restq-http/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | use http::{Method, Request}; 4 | use percent_encoding::percent_decode_str; 5 | pub use restq::{ 6 | parser::select, 7 | pom::parser::{sym, tag, Parser}, 8 | space, 9 | statement::{ 10 | column_def::{ColumnDef, Foreign}, 11 | parser::{delete, insert, update}, 12 | table_def, Delete, Insert, Select, Statement, TableDef, Update, Value, 13 | }, 14 | to_chars, CsvRows, DataValue, Error, StmtData, 15 | }; 16 | use std::io::Cursor; 17 | 18 | /// Parse into SQL Statement AST from http::Request 19 | pub fn parse_statement( 20 | request: &Request, 21 | ) -> Result<(Statement, Vec>), Error> { 22 | let method = request.method(); 23 | let url = extract_path_and_query(request); 24 | let body = request.body().as_bytes().to_vec(); 25 | parse_statement_from_parts(method, &url, Some(body)) 26 | } 27 | 28 | fn parse_statement_from_parts( 29 | method: &Method, 30 | url: &str, 31 | body: Option>, 32 | ) -> Result<(Statement, Vec>), Error> { 33 | let csv_data = csv_data_from_parts(&method, url, body)?; 34 | let statement = csv_data.statement(); 35 | let csv_rows = csv_data.rows_iter(); 36 | 37 | let data_values: Vec> = if let Some(csv_rows) = csv_rows { 38 | csv_rows.into_iter().collect() 39 | } else { 40 | vec![] 41 | }; 42 | 43 | Ok((statement, data_values)) 44 | } 45 | 46 | fn extract_path_and_query(request: &Request) -> String { 47 | let pnq = request 48 | .uri() 49 | .path_and_query() 50 | .map(|pnq| pnq.as_str()) 51 | .unwrap_or("/"); 52 | percent_decode_str(pnq).decode_utf8_lossy().to_string() 53 | } 54 | 55 | pub fn extract_restq_from_request(request: &Request) -> String { 56 | let method = request.method(); 57 | let url = extract_path_and_query(request); 58 | let prefix = method_to_prefix(method); 59 | format!("{} {}\n", prefix, url) 60 | } 61 | 62 | fn method_to_prefix(method: &Method) -> &'static str { 63 | match *method { 64 | Method::GET => "GET", 65 | Method::PUT => "PUT", 66 | Method::POST => "POST", 67 | Method::PATCH => "PATCH", 68 | Method::DELETE => "DELETE", 69 | Method::HEAD => "HEAD", 70 | Method::OPTIONS => todo!(), 71 | Method::TRACE => todo!("use this for database connection checking"), 72 | Method::CONNECT => { 73 | todo!("maybe used this for precaching/db_url connect") 74 | } 75 | _ => { 76 | let _ext = method.as_str(); 77 | todo!("Support for DROP, PURGE, ALTER, CREATE here") 78 | } 79 | } 80 | } 81 | 82 | /// Parse into SQL Statement AST from separate parts 83 | /// this is useful when using a different crate for the http request 84 | pub fn csv_data_from_parts( 85 | method: &Method, 86 | url: &str, 87 | body: Option>, 88 | ) -> Result>>, Error> { 89 | let prefix = method_to_prefix(method); 90 | let mut prefixed_url_and_body = 91 | format!("{} {}\n", prefix, url).into_bytes(); 92 | println!( 93 | "url_with_body: {}", 94 | String::from_utf8_lossy(&prefixed_url_and_body) 95 | ); 96 | body.map(|body| prefixed_url_and_body.extend(body)); 97 | Ok(StmtData::from_reader(Cursor::new(prefixed_url_and_body))?) 98 | } 99 | 100 | #[cfg(test)] 101 | mod tests { 102 | use super::*; 103 | use http::Request; 104 | use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; 105 | use restq::{ 106 | statement::{ 107 | column_def::{ColumnAttribute, ColumnDef, DataTypeDef}, 108 | ColumnName, TableDef, TableLookup, TableName, 109 | }, 110 | DataType, 111 | }; 112 | 113 | #[test] 114 | fn test_parse_create_statement() { 115 | let url = "product{*product_id:s32,@name:text,description:text,updated:utc,created_by(users):u32,@is_active:bool}"; 116 | let url = utf8_percent_encode(url, NON_ALPHANUMERIC).to_string(); 117 | let url = format!("http://localhost:8000/{}", url); 118 | println!("url: {}", url); 119 | let req = Request::builder() 120 | .method("PUT") 121 | .uri(&url) 122 | .body( 123 | "1,go pro,a slightly used go pro, 2019-10-31 10:10:10.1\n\ 124 | 2,shovel,a slightly used shovel, 2019-11-11 11:11:11.2\n\ 125 | " 126 | .to_string(), 127 | ) 128 | .unwrap(); 129 | 130 | let (statement, _rows) = parse_statement(&req).expect("must not fail"); 131 | 132 | println!("statement: {:#?}", statement); 133 | 134 | let users_table = TableDef { 135 | table: TableName { 136 | name: "users".into(), 137 | }, 138 | columns: vec![ColumnDef { 139 | column: ColumnName { 140 | name: "user_id".into(), 141 | }, 142 | attributes: Some(vec![ColumnAttribute::Primary]), 143 | data_type_def: DataTypeDef { 144 | data_type: DataType::U64, 145 | is_optional: false, 146 | default: None, 147 | }, 148 | foreign: None, 149 | }], 150 | }; 151 | let mut table_lookup = TableLookup::new(); 152 | table_lookup.add_table(users_table); 153 | assert_eq!( 154 | statement 155 | .into_sql_statement(Some(&table_lookup)) 156 | .expect("must not fail") 157 | .to_string(), 158 | "CREATE TABLE IF NOT EXISTS product (product_id INT PRIMARY KEY NOT NULL, name TEXT NOT NULL, description TEXT NOT NULL, updated TIMESTAMP NOT NULL, created_by INT NOT NULL REFERENCES users (user_id), is_active BOOLEAN NOT NULL)" 159 | ); 160 | } 161 | 162 | #[test] 163 | fn test_parse_select_statement() { 164 | let url = "person-><-users{name,age,class}?(age=gt.42&student=eq.true)|(gender=eq.`M`&is_active=true)&group_by=sum(age),grade,gender&having=min(age)=gte.42&order_by=age.desc,height.asc&page=2&page_size=10"; 165 | let url = utf8_percent_encode(url, NON_ALPHANUMERIC).to_string(); 166 | let url = format!("http://localhost:8000/{}", url); 167 | println!("url: {}", url); 168 | let req = Request::builder() 169 | .method("GET") 170 | .uri(&url) 171 | .body("".to_string()) 172 | .unwrap(); 173 | let (statement, _rows) = parse_statement(&req).expect("must not fail"); 174 | println!("statement: {:#?}", statement); 175 | 176 | let person_table = TableDef { 177 | table: TableName { 178 | name: "person".into(), 179 | }, 180 | columns: vec![ColumnDef { 181 | column: ColumnName { name: "id".into() }, 182 | attributes: Some(vec![ColumnAttribute::Primary]), 183 | data_type_def: DataTypeDef { 184 | data_type: DataType::S64, 185 | is_optional: false, 186 | default: None, 187 | }, 188 | foreign: None, 189 | }], 190 | }; 191 | let users_table = TableDef { 192 | table: TableName { 193 | name: "users".into(), 194 | }, 195 | columns: vec![ColumnDef { 196 | column: ColumnName { 197 | name: "person_id".into(), 198 | }, 199 | attributes: None, 200 | data_type_def: DataTypeDef { 201 | data_type: DataType::U64, 202 | is_optional: false, 203 | default: None, 204 | }, 205 | foreign: Some(Foreign { 206 | table: TableName { 207 | name: "person".into(), 208 | }, 209 | column: Some(ColumnName { name: "id".into() }), 210 | }), 211 | }], 212 | }; 213 | let mut table_lookup = TableLookup::new(); 214 | table_lookup.add_table(person_table); 215 | table_lookup.add_table(users_table); 216 | assert_eq!( 217 | statement 218 | .into_sql_statement(Some(&table_lookup)) 219 | .unwrap() 220 | .to_string(), 221 | "SELECT name, age, class FROM person JOIN users ON users.person_id = person.id WHERE (age > 42 AND student = true) OR (gender = 'M' AND is_active = true) GROUP BY sum(age), grade, gender HAVING min(age) >= 42 ORDER BY age DESC, height ASC LIMIT 10 OFFSET 10" 222 | ); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use restq::*; 2 | 3 | fn main() { 4 | let input = "person{person_id,name,updated}?age=lt.42&(student=eq.true|gender=eq.'M')&group_by=sum(age),grade,gender&having=min(age)=gt.42&order_by=age.desc,height.asc&page=20&page_size=100"; 5 | let input_chars = to_chars(input); 6 | let ret = select().parse(&input_chars).expect("must be parsed"); 7 | println!("ret: {:#?}", ret); 8 | println!("ret sql: {}", ret.to_string()); 9 | let sql = ret.into_sql_statement(None).unwrap(); 10 | println!("sql: {}", sql.to_string()); 11 | } 12 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | 2 | publish-restq: 3 | cargo publish -p restq 4 | 5 | publish: publish-restq 6 | cargo publish -p restq-http 7 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Use unstable features 2 | unstable_features = true 3 | 4 | max_width = 80 5 | 6 | ## Visually align, useful in writing the view 7 | indent_style = "Block" 8 | imports_indent = "Block" 9 | reorder_imports = true 10 | reorder_impl_items = true 11 | merge_imports = true 12 | ## I want to be able to delete unused imports easily 13 | imports_layout = "Vertical" 14 | ## Default value is false, yet clipy keeps nagging on this 15 | use_field_init_shorthand = true 16 | 17 | ## also format macro 18 | format_macro_matchers = true 19 | force_multiline_blocks = true 20 | -------------------------------------------------------------------------------- /src/csv_rows.rs: -------------------------------------------------------------------------------- 1 | /// contains Row iterator for the csv data 2 | use crate::statement::Value; 3 | use csv::{ReaderBuilder, StringRecordsIntoIter}; 4 | use std::io::{BufReader, Read}; 5 | 6 | pub struct CsvRows 7 | where 8 | R: Read, 9 | { 10 | into_iter: StringRecordsIntoIter>, 11 | } 12 | 13 | impl CsvRows 14 | where 15 | R: Read, 16 | { 17 | pub fn new(input: BufReader) -> Self { 18 | let into_iter = ReaderBuilder::new() 19 | .has_headers(false) 20 | .from_reader(input) 21 | .into_records(); 22 | 23 | CsvRows { into_iter } 24 | } 25 | } 26 | 27 | impl Iterator for CsvRows 28 | where 29 | R: Read, 30 | { 31 | type Item = Vec; 32 | 33 | fn next(&mut self) -> Option { 34 | match self.into_iter.next() { 35 | Some(row) => match row { 36 | Ok(row) => { 37 | let data_values: Vec = row 38 | .iter() 39 | .map(|record| Value::String(record.to_string())) 40 | .collect(); 41 | Some(data_values) 42 | } 43 | Err(_e) => None, 44 | }, 45 | None => None, 46 | } 47 | } 48 | } 49 | 50 | #[cfg(test)] 51 | mod tests { 52 | use super::*; 53 | use crate::statement::Value; 54 | 55 | #[test] 56 | fn test_csv() { 57 | let data = "\ 58 | 1,go pro,a slightly used go pro, 2019-10-31 10:10:10\n\ 59 | 2,shovel,a slightly used shovel, 2019-11-11 11:11:11\n\ 60 | "; 61 | 62 | let csv_data = CsvRows::new(BufReader::new(data.as_bytes())); 63 | 64 | let rows: Vec> = csv_data.collect(); 65 | println!("rows: {:#?}", rows); 66 | assert_eq!(rows.len(), 2); 67 | } 68 | 69 | #[test] 70 | fn test_csv2() { 71 | let data = "\ 72 | 1,go pro,a slightly used go pro, 2019-10-31 10:10:10\n\ 73 | 2,shovel,a slightly used shovel, 2019-11-11 11:11:11\n\ 74 | "; 75 | 76 | let csv_data = CsvRows::new(BufReader::new(data.as_bytes())); 77 | 78 | let rows: Vec> = csv_data.collect(); 79 | println!("rows: {:#?}", rows); 80 | assert_eq!(rows.len(), 2); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/data_pane.rs: -------------------------------------------------------------------------------- 1 | use crate::data_value::cast_data_value; 2 | use crate::ColumnDef; 3 | use crate::CsvRows; 4 | use crate::DataValue; 5 | use crate::Error; 6 | use crate::PlainData; 7 | use std::fmt; 8 | use std::io::BufReader; 9 | use std::io::Cursor; 10 | use std::io::Write; 11 | 12 | #[derive(Debug)] 13 | pub struct DataPane { 14 | pub columns: Vec, 15 | pub row_values: Vec>, 16 | } 17 | 18 | impl DataPane { 19 | pub fn from_csv(csv: Vec) -> Result { 20 | let plain = PlainData::from_reader(Cursor::new(csv))?; 21 | Ok(DataPane::from(plain)) 22 | } 23 | 24 | pub fn show(&self) -> fmt::Result{ 25 | let mut f = String::new(); 26 | self.render(&mut f)?; 27 | println!("{}", f); 28 | Ok(()) 29 | } 30 | 31 | /// show in a tabular display 32 | pub fn render(&self, f: &mut dyn fmt::Write) -> fmt::Result { 33 | // column char counts 34 | let mut lengths: Vec = self 35 | .columns 36 | .iter() 37 | .map(|column| column.column.name.chars().count()) 38 | .collect(); 39 | 40 | for row in self.row_values.iter() { 41 | for (i, value) in row.iter().enumerate() { 42 | let value_chars = value.to_string().chars().count(); 43 | if value_chars > lengths[i] { 44 | lengths[i] = value_chars; 45 | } 46 | } 47 | } 48 | 49 | // top header border line 50 | write!(f, "+")?; 51 | for (i, _column) in self.columns.iter().enumerate(){ 52 | let width = lengths[i] + 4; 53 | write!(f, "{:- fmt::Result { 93 | write!(f, "{{")?; 94 | for (i, column) in self.columns.iter().enumerate() { 95 | if i > 0 { 96 | write!(f, ",")?; 97 | } 98 | write!(f, "{}", column)?; 99 | } 100 | write!(f, "}}")?; 101 | for row in self.row_values.iter() { 102 | write!(f, "\n")?; 103 | for (i, value) in row.iter().enumerate() { 104 | if i > 0 { 105 | write!(f, ",")?; 106 | } 107 | write!(f, "{}", value)?; 108 | } 109 | } 110 | Ok(()) 111 | } 112 | } 113 | 114 | impl From>>> for DataPane { 115 | fn from(plain_data: PlainData>>) -> Self { 116 | let PlainData { columns, rows } = plain_data; 117 | let row_values: Vec> = rows 118 | .map(|row| { 119 | row.iter() 120 | .enumerate() 121 | .map(|(i, value)| { 122 | let dt = columns[i].data_type(); 123 | cast_data_value(value, &dt) 124 | }) 125 | .collect() 126 | }) 127 | .collect(); 128 | DataPane { 129 | columns, 130 | row_values, 131 | } 132 | } 133 | } 134 | 135 | impl From for PlainData>> { 136 | fn from(data_pane: DataPane) -> Self { 137 | let DataPane { 138 | columns, 139 | row_values, 140 | } = data_pane; 141 | let mut buffer = Cursor::new(vec![]); 142 | for row in row_values { 143 | for (i, value) in row.iter().enumerate() { 144 | if i > 0 { 145 | write!(buffer, ",").unwrap(); 146 | } 147 | write!(buffer, "{value},").unwrap(); 148 | } 149 | } 150 | PlainData { 151 | columns, 152 | rows: CsvRows::new(BufReader::new(buffer)), 153 | } 154 | } 155 | } 156 | 157 | #[cfg(test)] 158 | mod tests { 159 | use super::*; 160 | 161 | #[test] 162 | fn test_data() { 163 | let data = "{*product_id:s32,@name:text,description:text,updated:utc,created_by(users):u32,@is_active:bool}\n\ 164 | 1,go pro,a slightly used go pro,2019-10-31T10:10:10+00:00,101,true\n\ 165 | 2,shovel,a slightly used shovel,2019-11-11T11:11:11+00:00,101,true\ 166 | "; 167 | 168 | let data_pane = DataPane::from_csv(data.as_bytes().to_vec()) 169 | .expect("must be valid"); 170 | data_pane.show().unwrap(); 171 | 172 | assert_eq!(data, format!("{}", data_pane)); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/data_type.rs: -------------------------------------------------------------------------------- 1 | use crate::{statement::parser::ident, Error}; 2 | use pom::parser::*; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// restq supports comprehensive data types 6 | /// based on rust and postgresql type, combined together 7 | /// format: [?](contraint) 8 | /// ? - indicates it is optional, nullable in database context 9 | /// example: 10 | /// text? - nullable text 11 | /// text(8..) - text with at least 8 characters long 12 | /// text(..255) - text must not be more than 255 characters long. 13 | /// u32(1) - u32 with default value of 1 14 | /// u32(>10) - check value should be greater than 10 15 | /// u32(10 Vec { 74 | vec![ 75 | DataType::Bool, 76 | DataType::S8, 77 | DataType::S16, 78 | DataType::S32, 79 | DataType::S64, 80 | DataType::F32, 81 | DataType::F64, 82 | DataType::U8, 83 | DataType::U16, 84 | DataType::U32, 85 | DataType::U64, 86 | DataType::I8, 87 | DataType::I16, 88 | DataType::I32, 89 | DataType::I64, 90 | DataType::Uuid, 91 | DataType::UuidRand, 92 | DataType::UuidSlug, 93 | DataType::Utc, 94 | DataType::Text, 95 | DataType::Ident, 96 | DataType::Url, 97 | DataType::Json, 98 | DataType::Bytes, 99 | ] 100 | } 101 | 102 | fn match_data_type(dt: &str) -> Result { 103 | match dt { 104 | "bool" => Ok(DataType::Bool), 105 | "s8" => Ok(DataType::S8), 106 | "s16" => Ok(DataType::S16), 107 | "s32" => Ok(DataType::S32), 108 | "s64" => Ok(DataType::S64), 109 | "u8" => Ok(DataType::U8), 110 | "u16" => Ok(DataType::U16), 111 | "u32" => Ok(DataType::U32), 112 | "u64" => Ok(DataType::U64), 113 | "i8" => Ok(DataType::I8), 114 | "i16" => Ok(DataType::I16), 115 | "i32" => Ok(DataType::I32), 116 | "i64" => Ok(DataType::I64), 117 | "f32" => Ok(DataType::F32), 118 | "f64" => Ok(DataType::F64), 119 | "uuid" => Ok(DataType::Uuid), 120 | "uuid_rand" => Ok(DataType::UuidRand), 121 | "uuid_slug" => Ok(DataType::UuidSlug), 122 | "utc" => Ok(DataType::Utc), 123 | "text" => Ok(DataType::Text), 124 | "ident" => Ok(DataType::Ident), 125 | "url" => Ok(DataType::Url), 126 | "json" => Ok(DataType::Json), 127 | "bytes" => Ok(DataType::Bytes), 128 | _ => Err(Error::InvalidDataType(dt.to_string())), 129 | } 130 | } 131 | 132 | /// returns true if type is numeric or not 133 | pub fn is_numeric(&self) -> bool { 134 | match self { 135 | DataType::S8 136 | | DataType::S16 137 | | DataType::S32 138 | | DataType::S64 139 | | DataType::F32 140 | | DataType::F64 141 | | DataType::U8 142 | | DataType::U16 143 | | DataType::U32 144 | | DataType::U64 145 | | DataType::I8 146 | | DataType::I16 147 | | DataType::I32 148 | | DataType::I64 => true, 149 | _ => false, 150 | } 151 | } 152 | 153 | pub fn is_autogenerate(&self) -> bool { 154 | match self { 155 | DataType::S8 | DataType::S16 | DataType::S32 | DataType::S64 => { 156 | true 157 | } 158 | DataType::UuidRand | DataType::UuidSlug => true, 159 | _ => false, 160 | } 161 | } 162 | } 163 | 164 | pub fn data_type<'a>() -> Parser<'a, char, DataType> { 165 | ident().convert(|v| DataType::match_data_type(&v)) 166 | } 167 | 168 | #[cfg(test)] 169 | mod tests { 170 | use super::*; 171 | use crate::statement::parser::utils::*; 172 | 173 | #[test] 174 | fn test_data_type() { 175 | let input = to_chars("s32"); 176 | let ret = data_type().parse(&input).expect("must be parsed"); 177 | println!("{:#?}", ret); 178 | assert_eq!(ret, DataType::S32); 179 | } 180 | 181 | #[test] 182 | fn test_invalid_data_type() { 183 | let input = to_chars("x32"); 184 | let ret = data_type().parse(&input); 185 | println!("{:#?}", ret); 186 | assert!(ret.is_err()); 187 | } 188 | 189 | #[test] 190 | fn test_invalid_more_data_type() { 191 | let input = to_chars("serial32"); 192 | let ret = data_type().parse(&input); 193 | println!("{:#?}", ret); 194 | assert!(ret.is_err()); 195 | let err = ret.err().unwrap(); 196 | println!("{}", err); 197 | assert!(err.to_string().contains(r#"InvalidDataType("serial32")"#)) 198 | } 199 | 200 | #[test] 201 | fn all_data_types() { 202 | let all = [ 203 | "bool", 204 | "s8", 205 | "s16", 206 | "s32", 207 | "s64", 208 | "u8", 209 | "u16", 210 | "u32", 211 | "u64", 212 | "i8", 213 | "i16", 214 | "i32", 215 | "i64", 216 | "f32", 217 | "f64", 218 | "uuid", 219 | "uuid_rand", 220 | "uuid_slug", 221 | "utc", 222 | "text", 223 | "ident", 224 | "url", 225 | ]; 226 | 227 | for d in all.iter() { 228 | println!("trying {}...", d); 229 | let input = to_chars(d); 230 | let ret = data_type().parse(&input).expect("must be parsed"); 231 | println!("{} = {:#?}", d, ret); 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/data_value.rs: -------------------------------------------------------------------------------- 1 | use crate::{data_type::DataType, statement::Value}; 2 | use base64::{engine::general_purpose::URL_SAFE, Engine}; 3 | use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc}; 4 | use serde::{Deserialize, Serialize}; 5 | use uuid::Uuid; 6 | 7 | /// strict data value 8 | /// where each has exact byte definitions, etc. 9 | #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)] 10 | pub enum DataValue { 11 | Nil, 12 | Bool(bool), 13 | S8(u8), 14 | S16(u16), 15 | S32(u32), 16 | S64(u64), 17 | F32(f32), 18 | F64(f64), 19 | U8(u8), 20 | U16(u16), 21 | U32(u32), 22 | U64(u64), 23 | I8(i8), 24 | I16(i16), 25 | I32(i32), 26 | I64(i64), 27 | Uuid(Uuid), 28 | UuidRand(Uuid), 29 | UuidSlug(String), 30 | Utc(DateTime), 31 | Text(String), 32 | Ident(String), 33 | Bytes(Vec), 34 | } 35 | 36 | impl DataValue { 37 | pub fn get_data_type(&self) -> Option { 38 | let dt = match self { 39 | DataValue::Nil => { 40 | return None; 41 | } 42 | DataValue::Bool(_) => DataType::Bool, 43 | DataValue::S8(_) => DataType::S8, 44 | DataValue::S16(_) => DataType::S16, 45 | DataValue::S32(_) => DataType::S32, 46 | DataValue::S64(_) => DataType::S64, 47 | DataValue::F32(_) => DataType::F32, 48 | DataValue::F64(_) => DataType::F64, 49 | DataValue::U8(_) => DataType::U8, 50 | DataValue::U16(_) => DataType::U16, 51 | DataValue::U32(_) => DataType::U32, 52 | DataValue::U64(_) => DataType::U64, 53 | DataValue::I8(_) => DataType::I8, 54 | DataValue::I16(_) => DataType::I16, 55 | DataValue::I32(_) => DataType::I32, 56 | DataValue::I64(_) => DataType::I64, 57 | DataValue::Uuid(_) => DataType::Uuid, 58 | DataValue::UuidRand(_) => DataType::UuidRand, 59 | DataValue::UuidSlug(_) => DataType::UuidSlug, 60 | DataValue::Utc(_) => DataType::Utc, 61 | DataValue::Text(_) => DataType::Text, 62 | DataValue::Ident(_) => DataType::Ident, 63 | DataValue::Bytes(_) => DataType::Bytes, 64 | }; 65 | Some(dt) 66 | } 67 | } 68 | 69 | fn naive_date_parser(v: &str) -> NaiveDateTime { 70 | if let Ok(dt) = DateTime::parse_from_rfc3339(v) { 71 | dt.naive_local() 72 | } else if let Ok(ts) = 73 | NaiveDateTime::parse_from_str(&v, "%Y-%m-%dT%H:%M:%S") 74 | { 75 | ts 76 | } else if let Ok(ts) = 77 | NaiveDateTime::parse_from_str(&v, "%Y-%m-%dT%H:%M:%S%z") 78 | { 79 | ts 80 | } else if let Ok(ts) = 81 | NaiveDateTime::parse_from_str(&v, "%Y-%m-%dT%H:%M:%S%:z") 82 | { 83 | ts 84 | } else if let Ok(ts) = 85 | NaiveDateTime::parse_from_str(&v, "%Y-%m-%d %H:%M:%S") 86 | { 87 | ts 88 | } else if let Ok(ts) = 89 | NaiveDateTime::parse_from_str(&v, "%Y-%m-%d %H:%M:%S.%f") 90 | { 91 | ts 92 | } else if let Ok(nd) = NaiveDate::parse_from_str(&v, "%Y-%m-%d") { 93 | NaiveDateTime::new( 94 | nd, 95 | NaiveTime::from_hms_milli_opt(0, 0, 0, 0).unwrap(), 96 | ) 97 | } else { 98 | panic!("unable to parse timestamp: {:?}", v); 99 | } 100 | } 101 | 102 | impl Into for &DataValue { 103 | fn into(self) -> u32 { 104 | match self { 105 | DataValue::U8(v) => *v as u32, 106 | DataValue::U16(v) => *v as u32, 107 | DataValue::U32(v) => *v, 108 | DataValue::U64(v) => *v as u32, 109 | DataValue::I8(v) => *v as u32, 110 | DataValue::I16(v) => *v as u32, 111 | DataValue::I32(v) => *v as u32, 112 | DataValue::I64(v) => *v as u32, 113 | DataValue::S8(v) => *v as u32, 114 | DataValue::S16(v) => *v as u32, 115 | DataValue::S32(v) => *v, 116 | DataValue::S64(v) => *v as u32, 117 | _ => { 118 | panic!( 119 | "unsupported conversion: {:?} to u32", 120 | self.get_data_type() 121 | ) 122 | } 123 | } 124 | } 125 | } 126 | 127 | impl Into for &DataValue { 128 | fn into(self) -> u64 { 129 | match self { 130 | DataValue::U8(v) => *v as u64, 131 | DataValue::U16(v) => *v as u64, 132 | DataValue::U32(v) => *v as u64, 133 | DataValue::U64(v) => *v, 134 | DataValue::I8(v) => *v as u64, 135 | DataValue::I16(v) => *v as u64, 136 | DataValue::I32(v) => *v as u64, 137 | DataValue::I64(v) => *v as u64, 138 | DataValue::S8(v) => *v as u64, 139 | DataValue::S16(v) => *v as u64, 140 | DataValue::S32(v) => *v as u64, 141 | DataValue::S64(v) => *v, 142 | _ => { 143 | panic!( 144 | "unsupported conversion: {:?} to u64", 145 | self.get_data_type() 146 | ) 147 | } 148 | } 149 | } 150 | } 151 | 152 | impl Into for &DataValue { 153 | fn into(self) -> i64 { 154 | match self { 155 | DataValue::U8(v) => *v as i64, 156 | DataValue::U16(v) => *v as i64, 157 | DataValue::U32(v) => *v as i64, 158 | DataValue::U64(v) => *v as i64, 159 | DataValue::I8(v) => *v as i64, 160 | DataValue::I16(v) => *v as i64, 161 | DataValue::I32(v) => *v as i64, 162 | DataValue::I64(v) => *v, 163 | DataValue::S8(v) => *v as i64, 164 | DataValue::S16(v) => *v as i64, 165 | DataValue::S32(v) => *v as i64, 166 | DataValue::S64(v) => *v as i64, 167 | _ => { 168 | panic!( 169 | "unsupported conversion: {:?} to u64", 170 | self.get_data_type() 171 | ) 172 | } 173 | } 174 | } 175 | } 176 | 177 | impl Into for &DataValue { 178 | fn into(self) -> f32 { 179 | match self { 180 | DataValue::U8(v) => *v as f32, 181 | DataValue::U16(v) => *v as f32, 182 | DataValue::U32(v) => *v as f32, 183 | DataValue::U64(v) => *v as f32, 184 | DataValue::I8(v) => *v as f32, 185 | DataValue::I16(v) => *v as f32, 186 | DataValue::I32(v) => *v as f32, 187 | DataValue::I64(v) => *v as f32, 188 | DataValue::F32(v) => *v, 189 | DataValue::F64(v) => *v as f32, 190 | DataValue::S8(v) => *v as f32, 191 | DataValue::S16(v) => *v as f32, 192 | DataValue::S32(v) => *v as f32, 193 | DataValue::S64(v) => *v as f32, 194 | _ => { 195 | panic!( 196 | "unsupported conversion: {:?} to f32", 197 | self.get_data_type() 198 | ) 199 | } 200 | } 201 | } 202 | } 203 | 204 | impl Into for &DataValue { 205 | fn into(self) -> String { 206 | match self { 207 | DataValue::Text(v) => v.to_string(), 208 | _ => { 209 | panic!( 210 | "unsupported conversion: {:?} to String", 211 | self.get_data_type() 212 | ) 213 | } 214 | } 215 | } 216 | } 217 | 218 | impl Into for &DataValue { 219 | fn into(self) -> NaiveDateTime { 220 | match self { 221 | DataValue::Utc(v) => v.naive_utc(), 222 | _ => { 223 | panic!( 224 | "unsupported conversion: {:?} to NaiveDateTime", 225 | self.get_data_type() 226 | ) 227 | } 228 | } 229 | } 230 | } 231 | 232 | impl<'a> Into<&'a str> for &'a DataValue { 233 | fn into(self) -> &'a str { 234 | match self { 235 | DataValue::Text(ref v) => v, 236 | _ => { 237 | panic!( 238 | "unsupported conversion: {:?} to &str", 239 | self.get_data_type() 240 | ) 241 | } 242 | } 243 | } 244 | } 245 | 246 | impl Into for &DataValue { 247 | fn into(self) -> Value { 248 | match self { 249 | DataValue::Bool(v) => Value::Bool(*v), 250 | DataValue::U8(v) => Value::Number(*v as f64), 251 | DataValue::U16(v) => Value::Number(*v as f64), 252 | DataValue::U32(v) => Value::Number(*v as f64), 253 | DataValue::U64(v) => Value::Number(*v as f64), 254 | DataValue::I8(v) => Value::Number(*v as f64), 255 | DataValue::I16(v) => Value::Number(*v as f64), 256 | DataValue::I32(v) => Value::Number(*v as f64), 257 | DataValue::I64(v) => Value::Number(*v as f64), 258 | DataValue::F32(v) => Value::Number(*v as f64), 259 | DataValue::F64(v) => Value::Number(*v as f64), 260 | DataValue::Text(v) => Value::String(v.clone()), 261 | _ => panic!("todo for: {:?}", self), 262 | } 263 | } 264 | } 265 | 266 | /// cast the value into DataValue hinted by the data_type 267 | pub fn cast_data_value(value: &Value, required_type: &DataType) -> DataValue { 268 | if *value == Value::Null { 269 | DataValue::Nil 270 | } else { 271 | match *value { 272 | Value::Bool(v) => match *required_type { 273 | DataType::Bool => DataValue::Bool(v), 274 | DataType::U8 => DataValue::U8(if v { 1 } else { 0 }), 275 | DataType::U16 => DataValue::U16(if v { 1 } else { 0 }), 276 | DataType::U32 => DataValue::U32(if v { 1 } else { 0 }), 277 | DataType::U64 => DataValue::U64(if v { 1 } else { 0 }), 278 | DataType::I8 => DataValue::I8(if v { 1 } else { 0 }), 279 | DataType::I16 => DataValue::I16(if v { 1 } else { 0 }), 280 | DataType::I32 => DataValue::I32(if v { 1 } else { 0 }), 281 | DataType::I64 => DataValue::I64(if v { 1 } else { 0 }), 282 | DataType::S8 => DataValue::S8(if v { 1 } else { 0 }), 283 | DataType::S16 => DataValue::S16(if v { 1 } else { 0 }), 284 | DataType::S32 => DataValue::S32(if v { 1 } else { 0 }), 285 | DataType::S64 => DataValue::S64(if v { 1 } else { 0 }), 286 | _ => { 287 | panic!( 288 | "unsupported conversion from {:?} to {:?}", 289 | value, required_type 290 | ) 291 | } 292 | }, 293 | Value::Number(v) => match *required_type { 294 | DataType::U8 => DataValue::U8(v as u8), 295 | DataType::U16 => DataValue::U16(v as u16), 296 | DataType::U32 => DataValue::U32(v as u32), 297 | DataType::U64 => DataValue::U64(v as u64), 298 | DataType::I8 => DataValue::I8(v as i8), 299 | DataType::I16 => DataValue::I16(v as i16), 300 | DataType::I32 => DataValue::I32(v as i32), 301 | DataType::I64 => DataValue::I64(v as i64), 302 | DataType::F32 => DataValue::F32(v as f32), 303 | DataType::F64 => DataValue::F64(v as f64), 304 | DataType::S8 => DataValue::S8(v as u8), 305 | DataType::S16 => DataValue::S16(v as u16), 306 | DataType::S32 => DataValue::S32(v as u32), 307 | DataType::S64 => DataValue::S64(v as u64), 308 | _ => { 309 | panic!( 310 | "unsupported conversion from {:?} to {:?}", 311 | value, required_type 312 | ) 313 | } 314 | }, 315 | Value::String(ref v) => { 316 | match *required_type { 317 | DataType::Text => DataValue::Text(v.to_string()), 318 | DataType::Bool => match v.as_ref() { 319 | "true" => DataValue::Bool(true), 320 | "false" => DataValue::Bool(false), 321 | "1" => DataValue::Bool(true), 322 | "0" => DataValue::Bool(false), 323 | _ => DataValue::Bool(false), 324 | }, 325 | DataType::S8 => { 326 | if v.is_empty() { 327 | DataValue::Nil 328 | } else if let Ok(v) = v.parse::() { 329 | DataValue::S8(v) 330 | } else { 331 | panic!( 332 | "unsupported conversion from {:?} to {:?}", 333 | value, required_type 334 | ); 335 | } 336 | } 337 | DataType::S16 => { 338 | if v.is_empty() { 339 | DataValue::Nil 340 | } else if let Ok(v) = v.parse::() { 341 | DataValue::S16(v) 342 | } else { 343 | panic!( 344 | "unsupported conversion from {:?} to {:?}", 345 | value, required_type 346 | ); 347 | } 348 | } 349 | DataType::S32 => { 350 | if v.is_empty() { 351 | DataValue::Nil 352 | } else if let Ok(v) = v.parse::() { 353 | DataValue::S32(v) 354 | } else { 355 | panic!( 356 | "unsupported conversion from {:?} to {:?}", 357 | value, required_type 358 | ); 359 | } 360 | } 361 | DataType::S64 => { 362 | if v.is_empty() { 363 | DataValue::Nil 364 | } else if let Ok(v) = v.parse::() { 365 | DataValue::S64(v) 366 | } else { 367 | panic!( 368 | "unsupported conversion from {:?} to {:?}", 369 | value, required_type 370 | ); 371 | } 372 | } 373 | DataType::U8 => { 374 | if v.is_empty() { 375 | DataValue::Nil 376 | } else if v.is_empty() { 377 | DataValue::Nil 378 | } else if let Ok(v) = v.parse::() { 379 | DataValue::U8(v) 380 | } else { 381 | panic!( 382 | "unsupported conversion from {:?} to {:?}", 383 | value, required_type 384 | ); 385 | } 386 | } 387 | DataType::U16 => { 388 | if v.is_empty() { 389 | DataValue::Nil 390 | } else if let Ok(v) = v.parse::() { 391 | DataValue::U16(v) 392 | } else { 393 | panic!( 394 | "unsupported conversion from {:?} to {:?}", 395 | value, required_type 396 | ); 397 | } 398 | } 399 | DataType::U32 => { 400 | if v.is_empty() { 401 | DataValue::Nil 402 | } else if let Ok(v) = v.parse::() { 403 | DataValue::U32(v) 404 | } else { 405 | panic!( 406 | "unsupported conversion from {:?} to {:?}", 407 | value, required_type 408 | ); 409 | } 410 | } 411 | DataType::U64 => { 412 | if v.is_empty() { 413 | DataValue::Nil 414 | } else if let Ok(v) = v.parse::() { 415 | DataValue::U64(v) 416 | } else { 417 | panic!( 418 | "unsupported conversion from {:?} to {:?}", 419 | value, required_type 420 | ); 421 | } 422 | } 423 | DataType::I8 => { 424 | if v.is_empty() { 425 | DataValue::Nil 426 | } else if let Ok(v) = v.parse::() { 427 | DataValue::I8(v) 428 | } else { 429 | panic!( 430 | "unsupported conversion from {:?} to {:?}", 431 | value, required_type 432 | ); 433 | } 434 | } 435 | DataType::I16 => { 436 | if v.is_empty() { 437 | DataValue::Nil 438 | } else if let Ok(v) = v.parse::() { 439 | DataValue::I16(v) 440 | } else { 441 | panic!( 442 | "unsupported conversion from {:?} to {:?}", 443 | value, required_type 444 | ); 445 | } 446 | } 447 | DataType::I32 => { 448 | if v.is_empty() { 449 | DataValue::Nil 450 | } else if let Ok(v) = v.parse::() { 451 | DataValue::I32(v) 452 | } else { 453 | panic!( 454 | "unsupported conversion from {:?} to {:?}", 455 | value, required_type 456 | ); 457 | } 458 | } 459 | DataType::I64 => { 460 | if v.is_empty() { 461 | DataValue::Nil 462 | } else if let Ok(v) = v.parse::() { 463 | DataValue::I64(v) 464 | } else { 465 | panic!( 466 | "unsupported conversion from {:?} to {:?}", 467 | value, required_type 468 | ); 469 | } 470 | } 471 | DataType::F32 => { 472 | if v.is_empty() { 473 | DataValue::Nil 474 | } else if let Ok(v) = v.parse::() { 475 | DataValue::F32(v) 476 | } else { 477 | panic!( 478 | "unsupported conversion from {:?} to {:?}", 479 | value, required_type 480 | ); 481 | } 482 | } 483 | DataType::F64 => { 484 | if v.is_empty() { 485 | DataValue::Nil 486 | } else if let Ok(v) = v.parse::() { 487 | DataValue::F64(v) 488 | } else { 489 | panic!( 490 | "unsupported conversion from {:?} to {:?}", 491 | value, required_type 492 | ); 493 | } 494 | } 495 | DataType::Utc => { 496 | if v.is_empty() { 497 | DataValue::Nil 498 | } else { 499 | let ts = naive_date_parser(&v); 500 | if let Some(utc) = 501 | DateTime::::from_timestamp_millis( 502 | ts.and_utc().timestamp_millis(), 503 | ) 504 | { 505 | DataValue::Utc(utc) 506 | } else { 507 | panic!("error converting datetime"); 508 | } 509 | } 510 | } 511 | DataType::Uuid | DataType::UuidRand => { 512 | match Uuid::parse_str(&v) { 513 | Ok(v) => DataValue::Uuid(v), 514 | Err(_e) => DataValue::Nil, 515 | } 516 | } 517 | DataType::UuidSlug => DataValue::UuidSlug(v.to_string()), 518 | //TODO: validate identifier 519 | DataType::Ident => DataValue::Ident(v.to_string()), 520 | DataType::Bytes => { 521 | let bytes = URL_SAFE 522 | .decode(&v) 523 | .expect("must be a valid base64 bytes"); 524 | DataValue::Bytes(bytes) 525 | } 526 | DataType::Json => DataValue::Text(v.to_string()), 527 | _ => { 528 | panic!( 529 | "unsupported conversion from {:?} to {:?}", 530 | value, required_type 531 | ) 532 | } 533 | } 534 | } 535 | _ => { 536 | panic!( 537 | "unsupported conversion from {:?} to {:?}", 538 | value, required_type 539 | ) 540 | } 541 | } 542 | } 543 | } 544 | 545 | #[cfg(test)] 546 | mod test { 547 | use super::*; 548 | 549 | #[test] 550 | fn parse_simple_date() { 551 | let date = "2006-02-14"; 552 | 553 | let parsed = NaiveDateTime::parse_from_str(date, "%Y-%m-%d"); 554 | println!("parsed: {:?}", parsed); 555 | 556 | let res = naive_date_parser(date); 557 | let naive_date = NaiveDate::from_ymd_opt(2006, 2, 14).unwrap(); 558 | let naive_time = NaiveTime::from_hms_milli_opt(0, 0, 0, 0).unwrap(); 559 | assert_eq!(res, NaiveDateTime::new(naive_date, naive_time)); 560 | } 561 | 562 | #[test] 563 | fn parse_dates() { 564 | let date = "2006-02-15T09:34:33+00:00"; 565 | let res = naive_date_parser(date); 566 | println!("res: {}", res); 567 | let naive_date = NaiveDate::from_ymd_opt(2006, 2, 15).unwrap(); 568 | let naive_time = NaiveTime::from_hms_milli_opt(9, 34, 33, 0).unwrap(); 569 | assert_eq!(res, NaiveDateTime::new(naive_date, naive_time)); 570 | } 571 | 572 | #[test] 573 | fn parse_naive_dates() { 574 | let date = "2006-02-15T09:34:33"; 575 | let res = naive_date_parser(date); 576 | println!("res: {}", res); 577 | let naive_date = NaiveDate::from_ymd_opt(2006, 2, 15).unwrap(); 578 | let naive_time = NaiveTime::from_hms_milli_opt(9, 34, 33, 0).unwrap(); 579 | assert_eq!(res, NaiveDateTime::new(naive_date, naive_time)); 580 | } 581 | } 582 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(warnings)] 2 | 3 | mod csv_rows; 4 | mod data_pane; 5 | mod data_type; 6 | pub mod data_value; 7 | pub mod multi_stmt; 8 | pub mod plain_data; 9 | pub mod statement; 10 | pub mod stmt_data; 11 | //reexport sql-ast 12 | pub use sqlparser as sql; 13 | 14 | pub use chrono; 15 | pub use csv_rows::CsvRows; 16 | pub use data_pane::DataPane; 17 | pub use data_type::DataType; 18 | pub use data_value::DataValue; 19 | pub use multi_stmt::MultiStatement; 20 | pub use plain_data::PlainData; 21 | pub use pom; 22 | pub use statement::{ 23 | parser, 24 | parser::{ 25 | filter_expr, select, 26 | utils::{bytes_to_chars, space, to_chars}, 27 | }, 28 | table_def, ColumnDef, ColumnName, Expr, Operator, Select, TableDef, 29 | TableError, TableName, Value, 30 | }; 31 | pub use stmt_data::{parse_select_chars, StmtData}; 32 | use thiserror::Error; 33 | pub use uuid::Uuid; 34 | 35 | #[derive(Error, Debug)] 36 | pub enum Error { 37 | #[error("ParseError: {0}")] 38 | ParseError(#[from] pom::Error), 39 | #[error("Invalid DataType: {0}")] 40 | InvalidDataType(String), 41 | #[error("{0}")] 42 | TableError(#[from] TableError), 43 | #[error("GenericError: {0}")] 44 | GenericError(String), 45 | #[error("More than 1 statement is generated")] 46 | MoreThanOneStatement, 47 | #[error("{0}")] 48 | IoError(#[from] std::io::Error), 49 | } 50 | -------------------------------------------------------------------------------- /src/multi_stmt.rs: -------------------------------------------------------------------------------- 1 | use crate::StmtData; 2 | use std::io::{BufRead, BufReader, Cursor, Read, Seek, SeekFrom}; 3 | 4 | /// A statement iterator, lazily parse statement with data 5 | pub struct MultiStatement { 6 | content: BufReader, 7 | } 8 | 9 | impl MultiStatement 10 | where 11 | R: Read + Seek, 12 | { 13 | pub fn from_reader(reader: R) -> Self { 14 | MultiStatement { 15 | content: BufReader::new(reader), 16 | } 17 | } 18 | 19 | pub fn statement_iter(self) -> StatementIter { 20 | StatementIter { 21 | content: self.content, 22 | } 23 | } 24 | } 25 | 26 | pub struct StatementIter { 27 | content: BufReader, 28 | } 29 | 30 | impl StatementIter 31 | where 32 | R: Read + Seek, 33 | { 34 | pub fn new(content: BufReader) -> Self { 35 | StatementIter { content } 36 | } 37 | 38 | fn ignore_whitespace(&mut self) { 39 | let mut buffer = String::new(); 40 | while let Ok(n) = self.content.read_line(&mut buffer) { 41 | if buffer.trim().is_empty() { 42 | //ignoring blank lines 43 | } else { 44 | // seek to the last non blank character than break the loop 45 | self.content 46 | .seek(SeekFrom::Current(-(n as i64))) 47 | .expect("must seek"); 48 | break; 49 | } 50 | if n == 0 { 51 | break; 52 | } 53 | } 54 | } 55 | } 56 | 57 | impl Iterator for StatementIter 58 | where 59 | R: Read + Seek, 60 | { 61 | type Item = StmtData>>; 62 | 63 | fn next(&mut self) -> Option { 64 | self.ignore_whitespace(); 65 | let mut buffer = vec![]; 66 | while let Ok(n) = self.content.read_until(b'\n', &mut buffer) { 67 | let last_char = buffer.iter().last(); 68 | if (n == 1 && last_char == Some(&b'\n')) || n == 0 { 69 | if !buffer.is_empty() { 70 | let stmt = StmtData::from_reader(Cursor::new(buffer)) 71 | .expect("must not error"); 72 | return Some(stmt); 73 | } else { 74 | return None; 75 | } 76 | } 77 | if n == 0 { 78 | break; 79 | } 80 | } 81 | None 82 | } 83 | } 84 | 85 | #[cfg(test)] 86 | mod tests { 87 | use super::*; 88 | use crate::statement::*; 89 | 90 | #[test] 91 | fn test_multi_statement() { 92 | let data = r#"PUT /+category{*category_id:s32,name:text,description:text?,slug:text?,topic:i32?,created_at:utc,updated:utc} 93 | 1,Staff,staff,Private categories for staff discussion. Topics are only visible to admin and moderators 94 | 2,Technology,tecnology,Anything related to technology 95 | 96 | PUT /+topic{*topic:s32,title:text,excerpt:text?,created_at:utc(now()),updated_at:utc?} 97 | 1,About Euphorum,1 98 | 3,Topic3,3 99 | 2,Welcome to Euphorum,2"#; 100 | 101 | let ms = MultiStatement::from_reader(Cursor::new(data.as_bytes())); 102 | let mut iter = ms.statement_iter(); 103 | let stmt1 = iter.next().expect("must have a next"); 104 | if let Statement::Create(create_cat) = stmt1.statement() { 105 | println!("create1: {}", create_cat); 106 | } 107 | let data1 = stmt1.rows_iter().expect("must have csv rows"); 108 | let all_data1 = data1.collect::>(); 109 | assert_eq!(all_data1.len(), 2); 110 | 111 | let stmt2 = iter.next().expect("must have a next"); 112 | if let Statement::Create(create_topic) = stmt2.statement() { 113 | println!("create2: {}", create_topic); 114 | } 115 | let data2 = stmt2.rows_iter().expect("must have csv rows"); 116 | let all_data2 = dbg!(data2.collect::>()); 117 | assert_eq!(all_data2.len(), 3); 118 | } 119 | 120 | #[test] 121 | fn test_multi_statement_with_multiple_empty_lines() { 122 | let data = r#" 123 | 124 | PUT /+category{*category_id:s32,name:text,description:text?,slug:text?,topic:i32?,created_at:utc,updated:utc} 125 | 1,Staff,staff,Private categories for staff discussion. Topics are only visible to admin and moderators 126 | 2,Technology,tecnology,Anything related to technology 127 | 128 | 129 | 130 | PUT /+topic{*topic:s32,title:text,excerpt:text?,created_at:utc(now()),updated_at:utc?} 131 | 1,About Euphorum,1 132 | 3,Topic3,3 133 | 2,Welcome to Euphorum,2"#; 134 | 135 | let ms = MultiStatement::from_reader(Cursor::new(data.as_bytes())); 136 | let mut iter = ms.statement_iter(); 137 | let stmt1 = iter.next().expect("must have a next"); 138 | if let Statement::Create(create_cat) = stmt1.statement() { 139 | println!("create1: {}", create_cat); 140 | } 141 | let data1 = stmt1.rows_iter().expect("must have csv rows"); 142 | let all_data1 = data1.collect::>(); 143 | assert_eq!(all_data1.len(), 2); 144 | 145 | let stmt2 = iter.next().expect("must have a next"); 146 | if let Statement::Create(create_topic) = stmt2.statement() { 147 | println!("create2: {}", create_topic); 148 | } 149 | let data2 = stmt2.rows_iter().expect("must have csv rows"); 150 | let all_data2 = dbg!(data2.collect::>()); 151 | assert_eq!(all_data2.len(), 3); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/plain_data.rs: -------------------------------------------------------------------------------- 1 | //! Plain csv data 2 | //! contains only the table definition and the csv data 3 | use crate::{ 4 | statement::{parser, parser::utils::bytes_to_chars}, 5 | ColumnDef, CsvRows, 6 | }; 7 | use std::io::{BufRead, BufReader, Read}; 8 | 9 | /// contains just the table definition and the csv data 10 | pub struct PlainData 11 | where 12 | R: Read, 13 | { 14 | pub columns: Vec, 15 | pub rows: CsvRows, 16 | } 17 | 18 | impl PlainData 19 | where 20 | R: Read, 21 | { 22 | pub fn from_reader(reader: R) -> Result { 23 | let mut bufread = BufReader::new(reader); 24 | let mut first_line = vec![]; 25 | let _header_len = bufread.read_until(b'\n', &mut first_line)?; 26 | 27 | let header_input = bytes_to_chars(&first_line); 28 | let column_defs = 29 | parser::enclosed_column_def_list().parse(&header_input)?; 30 | 31 | Ok(PlainData { 32 | columns: column_defs, 33 | rows: CsvRows::new(bufread), 34 | }) 35 | } 36 | 37 | /// consume self and return as csv rows iterator 38 | pub fn rows_iter(self) -> CsvRows { 39 | self.rows 40 | } 41 | } 42 | 43 | #[cfg(test)] 44 | mod tests { 45 | use super::*; 46 | use crate::statement::Value; 47 | 48 | #[test] 49 | fn test_plain_data() { 50 | let data = "{*product_id:s32,@name:text,description:text,updated:utc,created_by(users):u32,@is_active:bool}\n\ 51 | 1,go pro,a slightly used go pro, 2019-10-31 10:10:10\n\ 52 | 2,shovel,a slightly used shovel, 2019-11-11 11:11:11\n\ 53 | "; 54 | 55 | let csv_data = 56 | PlainData::from_reader(data.as_bytes()).expect("must be valid"); 57 | 58 | let rows: Vec> = csv_data.rows_iter().collect(); 59 | println!("rows: {:#?}", rows); 60 | assert_eq!(rows.len(), 2); 61 | } 62 | 63 | #[test] 64 | fn test_plain_data2() { 65 | let data = "{product_id:s32,name:text,description:text,updated:utc,created_by:u32,is_active:bool}\n\ 66 | 1,go pro,a slightly used go pro, 2019-10-31 10:10:10\n\ 67 | 2,shovel,a slightly used shovel, 2019-11-11 11:11:11\n\ 68 | "; 69 | 70 | let csv_data = 71 | PlainData::from_reader(data.as_bytes()).expect("must be valid"); 72 | 73 | let rows: Vec> = csv_data.rows_iter().collect(); 74 | println!("rows: {:#?}", rows); 75 | assert_eq!(rows.len(), 2); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/statement.rs: -------------------------------------------------------------------------------- 1 | pub mod column_def; 2 | mod display; 3 | mod expr; 4 | mod into_sql; 5 | mod operator; 6 | pub mod parser; 7 | mod table; 8 | pub mod table_def; 9 | mod value; 10 | 11 | pub use column_def::{ColumnDef, ColumnName}; 12 | pub use expr::{BinaryOperation, Expr, ExprRename}; 13 | pub use operator::Operator; 14 | use serde::{Deserialize, Serialize}; 15 | pub use table::{FromTable, JoinType, TableError, TableLookup}; 16 | pub use table_def::{TableDef, TableName}; 17 | pub use value::Value; 18 | 19 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 20 | pub enum Statement { 21 | Select(Select), 22 | Insert(Insert), 23 | Update(Update), 24 | BulkUpdate(BulkUpdate), 25 | Delete(Delete), 26 | BulkDelete(BulkDelete), 27 | Create(TableDef), 28 | DropTable(DropTable), 29 | AlterTable(AlterTable), 30 | } 31 | 32 | #[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)] 33 | pub struct Select { 34 | pub from_table: FromTable, 35 | pub filter: Option, 36 | pub group_by: Option>, 37 | pub having: Option, 38 | pub projection: Option>, // column selection 39 | pub order_by: Option>, 40 | pub range: Option, 41 | } 42 | 43 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 44 | pub struct Insert { 45 | pub into: TableName, 46 | pub columns: Vec, 47 | pub source: Source, 48 | pub returning: Option>, 49 | } 50 | 51 | #[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)] 52 | pub struct AlterTable { 53 | pub table: TableName, 54 | pub alter_operations: Vec, 55 | } 56 | 57 | #[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)] 58 | pub struct DropTable { 59 | pub table: TableName, 60 | } 61 | 62 | // Note: Only one alter operation is allowed 63 | // on an alter table query. 64 | // So, if there are multiple alter opration 65 | // that needs to be executed, there will be multiple 66 | // alter table query that needs to be generated and executed 67 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 68 | pub enum AlterOperation { 69 | DropColumn(ColumnName), 70 | AddColumn(ColumnDef), 71 | AlterColumn(ColumnName, ColumnDef), 72 | } 73 | 74 | /// Insert can get data from a set of values 75 | /// or from a select statement 76 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 77 | pub enum Source { 78 | Select(Select), 79 | Values(Vec>), 80 | Parameterized(Vec), 81 | } 82 | 83 | /// DELETE /product?product_id=1 84 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 85 | pub struct Delete { 86 | pub from: TableName, 87 | pub condition: Option, 88 | } 89 | 90 | /// DELETE /product{product_id} 91 | /// 1 92 | /// 2 93 | /// 3 94 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 95 | pub struct BulkDelete { 96 | pub from: TableName, 97 | pub columns: Vec, 98 | pub values: Vec>, 99 | } 100 | 101 | /// PATCH /product{description="I'm the new description now"}?product_id=1 102 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 103 | pub struct Update { 104 | pub table: TableName, 105 | pub columns: Vec, 106 | pub values: Vec, // one value for each column 107 | pub condition: Option, 108 | } 109 | 110 | /// PATCH /product{*product_id,name} 111 | /// 1,go pro,1,go pro hero4 112 | /// 2,shovel,2,slightly used shovel 113 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 114 | pub struct BulkUpdate { 115 | pub table: TableName, 116 | pub columns: Vec, 117 | pub values: Vec>, 118 | } 119 | 120 | #[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)] 121 | pub struct Function { 122 | pub name: String, 123 | pub params: Vec, 124 | } 125 | 126 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 127 | pub struct Order { 128 | pub expr: Expr, 129 | pub direction: Option, 130 | } 131 | 132 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 133 | pub enum Direction { 134 | Asc, 135 | Desc, 136 | } 137 | 138 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 139 | pub enum Range { 140 | Page(Page), 141 | Limit(Limit), 142 | } 143 | 144 | #[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)] 145 | pub struct Page { 146 | pub page: i64, 147 | pub page_size: i64, 148 | } 149 | 150 | #[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)] 151 | pub struct Limit { 152 | pub limit: i64, 153 | pub offset: Option, 154 | } 155 | 156 | impl Range { 157 | pub(crate) fn limit(&self) -> i64 { 158 | match self { 159 | Range::Page(page) => page.page_size, 160 | Range::Limit(limit) => limit.limit, 161 | } 162 | } 163 | 164 | pub(crate) fn offset(&self) -> Option { 165 | match self { 166 | Range::Page(page) => Some((page.page - 1) * page.page_size), 167 | Range::Limit(limit) => limit.offset, 168 | } 169 | } 170 | } 171 | 172 | impl Into for Select { 173 | fn into(self) -> Statement { 174 | Statement::Select(self) 175 | } 176 | } 177 | 178 | impl Into for TableDef { 179 | fn into(self) -> Statement { 180 | Statement::Create(self) 181 | } 182 | } 183 | impl Into for DropTable { 184 | fn into(self) -> Statement { 185 | Statement::DropTable(self) 186 | } 187 | } 188 | 189 | impl Select { 190 | pub fn set_page(&mut self, page: i64, page_size: i64) { 191 | self.range = Some(Range::Page(Page { page, page_size })); 192 | } 193 | 194 | pub fn get_page(&self) -> Option { 195 | if let Some(Range::Page(page)) = &self.range { 196 | Some(page.page) 197 | } else { 198 | None 199 | } 200 | } 201 | 202 | pub fn get_page_size(&self) -> Option { 203 | if let Some(Range::Page(page)) = &self.range { 204 | Some(page.page_size) 205 | } else { 206 | None 207 | } 208 | } 209 | 210 | pub fn add_simple_filter( 211 | &mut self, 212 | column: ColumnName, 213 | operator: Operator, 214 | search_key: &str, 215 | ) { 216 | let simple_filter = Expr::BinaryOperation(Box::new(BinaryOperation { 217 | left: Expr::Column(column), 218 | operator, 219 | right: Expr::Value(Value::String(search_key.to_string())), 220 | })); 221 | 222 | //TODO: need to deal with existing filters 223 | self.filter = Some(simple_filter); 224 | } 225 | } 226 | 227 | impl Default for Direction { 228 | fn default() -> Self { 229 | Direction::Asc 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/statement/column_def.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | data_type::DataType, 3 | data_value::DataValue, 4 | statement::{Function, TableName}, 5 | }; 6 | use chrono::Utc; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 10 | pub struct ColumnDef { 11 | pub column: ColumnName, 12 | pub attributes: Option>, 13 | pub data_type_def: DataTypeDef, 14 | pub foreign: Option, 15 | } 16 | 17 | #[derive( 18 | Debug, 19 | PartialEq, 20 | Default, 21 | Clone, 22 | PartialOrd, 23 | Hash, 24 | Eq, 25 | Ord, 26 | Serialize, 27 | Deserialize, 28 | )] 29 | pub struct ColumnName { 30 | pub name: String, 31 | } 32 | 33 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 34 | pub struct Foreign { 35 | pub table: TableName, 36 | pub column: Option, 37 | } 38 | 39 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 40 | pub struct DataTypeDef { 41 | pub data_type: DataType, 42 | pub is_optional: bool, 43 | pub default: Option, 44 | } 45 | 46 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 47 | pub enum DefaultValue { 48 | DataValue(DataValue), 49 | Function(Function), 50 | } 51 | 52 | #[derive(Debug, PartialEq, Clone, Hash, Eq, Serialize, Deserialize)] 53 | pub enum ColumnAttribute { 54 | Primary, 55 | Unique, 56 | Index, 57 | } 58 | 59 | impl ColumnDef { 60 | pub fn is_primary(&self) -> bool { 61 | if let Some(attributes) = &self.attributes { 62 | attributes 63 | .iter() 64 | .any(|att| ColumnAttribute::Primary == *att) 65 | } else { 66 | false 67 | } 68 | } 69 | 70 | pub fn is_autoincrement_and_primary(&self) -> bool { 71 | self.is_primary() && self.data_type().is_autogenerate() 72 | } 73 | 74 | pub fn data_type(&self) -> DataType { 75 | self.data_type_def.data_type.clone() 76 | } 77 | 78 | /// returns true if this datatype definition have a generated value 79 | /// or a default value 80 | pub fn has_generated_default(&self) -> bool { 81 | self.data_type().is_autogenerate() 82 | | self.data_type_def.default.is_some() 83 | } 84 | 85 | /// create a data_value from this ColumnDef 86 | pub fn default_value(&self) -> DataValue { 87 | match &self.data_type_def.default { 88 | None => DataValue::Nil, 89 | Some(default) => match default { 90 | DefaultValue::DataValue(dv) => dv.clone(), 91 | DefaultValue::Function(df) => match &*df.name { 92 | "now" => DataValue::Utc(Utc::now()), 93 | "today" => DataValue::Utc(Utc::now()), 94 | _ => DataValue::Nil, 95 | }, 96 | }, 97 | } 98 | } 99 | } 100 | 101 | impl From for DefaultValue { 102 | fn from(v: DataValue) -> Self { 103 | DefaultValue::DataValue(v) 104 | } 105 | } 106 | 107 | impl From for DefaultValue { 108 | fn from(f: Function) -> Self { 109 | DefaultValue::Function(f) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/statement/display.rs: -------------------------------------------------------------------------------- 1 | use crate::statement::column_def::*; 2 | use crate::statement::*; 3 | use crate::*; 4 | use base64::{engine::general_purpose::URL_SAFE, Engine}; 5 | use std::fmt; 6 | 7 | /// This converts the restq ast into a string for use in the url 8 | impl fmt::Display for Select { 9 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 10 | self.from_table.fmt(f)?; 11 | if let Some(projection) = &self.projection { 12 | write!(f, "{{")?; 13 | for (i, expr) in projection.iter().enumerate() { 14 | if i > 0 { 15 | write!(f, ",")?; 16 | } 17 | expr.fmt(f)?; 18 | } 19 | write!(f, "}}")?; 20 | } 21 | 22 | if let Some(filter) = &self.filter { 23 | write!(f, "?")?; 24 | filter.fmt(f)?; 25 | } 26 | 27 | if let Some(group_by) = &self.group_by { 28 | write!(f, "&group_by=")?; 29 | for (i, expr) in group_by.iter().enumerate() { 30 | if i > 0 { 31 | write!(f, ",")?; 32 | } 33 | expr.fmt(f)?; 34 | } 35 | } 36 | 37 | if let Some(having) = &self.having { 38 | write!(f, "&having=")?; 39 | having.fmt(f)?; 40 | } 41 | if let Some(order_by) = &self.order_by { 42 | write!(f, "&order_by=")?; 43 | for (i, ord) in order_by.iter().enumerate() { 44 | if i > 0 { 45 | write!(f, ",")?; 46 | } 47 | ord.fmt(f)?; 48 | } 49 | } 50 | if let Some(range) = &self.range { 51 | write!(f, "&")?; 52 | range.fmt(f)?; 53 | } 54 | 55 | Ok(()) 56 | } 57 | } 58 | 59 | impl fmt::Display for Function { 60 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 61 | write!(f, "{}(", self.name)?; 62 | for (i, param) in self.params.iter().enumerate() { 63 | if i > 0 { 64 | write!(f, ",")?; 65 | } 66 | write!(f, "{}", param)?; 67 | } 68 | write!(f, ")") 69 | } 70 | } 71 | 72 | impl fmt::Display for ColumnName { 73 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 74 | write!(f, "{}", self.name) 75 | } 76 | } 77 | 78 | impl fmt::Display for Order { 79 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 80 | self.expr.fmt(f)?; 81 | if let Some(direction) = &self.direction { 82 | write!(f, ".")?; 83 | direction.fmt(f)?; 84 | } 85 | Ok(()) 86 | } 87 | } 88 | 89 | impl fmt::Display for Direction { 90 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 91 | match self { 92 | Direction::Asc => write!(f, "asc"), 93 | Direction::Desc => write!(f, "desc"), 94 | } 95 | } 96 | } 97 | 98 | impl fmt::Display for Range { 99 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 100 | match self { 101 | Range::Page(page) => page.fmt(f), 102 | Range::Limit(limit) => limit.fmt(f), 103 | } 104 | } 105 | } 106 | 107 | impl fmt::Display for Page { 108 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 109 | write!(f, "page={}&page_size={}", self.page, self.page_size) 110 | } 111 | } 112 | 113 | impl fmt::Display for Limit { 114 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 115 | write!(f, "limit={}", self.limit)?; 116 | if let Some(offset) = &self.offset { 117 | write!(f, "&offset={}", offset)?; 118 | } 119 | Ok(()) 120 | } 121 | } 122 | 123 | impl fmt::Display for DataTypeDef { 124 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 125 | self.data_type.fmt(f)?; 126 | if self.is_optional { 127 | write!(f, "?")?; 128 | } 129 | if let Some(default) = &self.default { 130 | write!(f, "({})", default)?; 131 | } 132 | Ok(()) 133 | } 134 | } 135 | 136 | impl fmt::Display for Foreign { 137 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 138 | self.table.fmt(f)?; 139 | if let Some(column) = &self.column { 140 | write!(f, "::{}", column)?; 141 | } 142 | Ok(()) 143 | } 144 | } 145 | 146 | impl fmt::Display for ColumnDef { 147 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 148 | if let Some(attrs) = &self.attributes { 149 | for att in attrs { 150 | att.fmt(f)?; 151 | } 152 | } 153 | self.column.fmt(f)?; 154 | if let Some(foreign) = &self.foreign { 155 | write!(f, "({})", foreign)?; 156 | } 157 | write!(f, ":")?; 158 | self.data_type_def.fmt(f)?; 159 | Ok(()) 160 | } 161 | } 162 | 163 | impl fmt::Display for ColumnAttribute { 164 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 165 | match self { 166 | ColumnAttribute::Primary => write!(f, "*"), 167 | ColumnAttribute::Unique => write!(f, "&"), 168 | ColumnAttribute::Index => write!(f, "@"), 169 | } 170 | } 171 | } 172 | 173 | impl fmt::Display for DefaultValue { 174 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 175 | match self { 176 | DefaultValue::DataValue(v) => v.fmt(f), 177 | DefaultValue::Function(v) => v.fmt(f), 178 | } 179 | } 180 | } 181 | 182 | impl fmt::Display for Value { 183 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 184 | match self { 185 | Value::Null => write!(f, "null"), 186 | Value::String(v) => write!(f, "'{}'", v), 187 | Value::Number(v) => write!(f, "{}", v), 188 | Value::Bool(v) => write!(f, "{}", v), 189 | } 190 | } 191 | } 192 | 193 | impl fmt::Display for Expr { 194 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 195 | match self { 196 | Expr::Column(column) => column.fmt(f), 197 | Expr::Function(function) => function.fmt(f), 198 | Expr::Value(value) => value.fmt(f), 199 | Expr::MultiValue(values) => { 200 | write!(f, "[")?; 201 | for (i, value) in values.iter().enumerate() { 202 | if i > 0 { 203 | write!(f, ",")?; 204 | } 205 | value.fmt(f)?; 206 | } 207 | write!(f, "]") 208 | } 209 | Expr::BinaryOperation(bop) => bop.fmt(f), 210 | Expr::Nested(expr) => write!(f, "({})", expr), 211 | } 212 | } 213 | } 214 | 215 | impl fmt::Display for BinaryOperation { 216 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 217 | if self.operator.needs_separator() { 218 | write!(f, "{}={}.{}", self.left, self.operator, self.right) 219 | } else { 220 | write!(f, "{}{}{}", self.left, self.operator, self.right) 221 | } 222 | } 223 | } 224 | 225 | impl fmt::Display for ExprRename { 226 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 227 | self.expr.fmt(f)?; 228 | if let Some(rename) = &self.rename { 229 | write!(f, "=>{}", rename)?; 230 | } 231 | Ok(()) 232 | } 233 | } 234 | 235 | impl fmt::Display for DataValue { 236 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 237 | match self { 238 | DataValue::Nil => write!(f, ""), 239 | DataValue::Bool(v) => write!(f, "{}", v), 240 | DataValue::S8(v) => write!(f, "{}", v), 241 | DataValue::S16(v) => write!(f, "{}", v), 242 | DataValue::S32(v) => write!(f, "{}", v), 243 | DataValue::S64(v) => write!(f, "{}", v), 244 | DataValue::F32(v) => write!(f, "{}", v), 245 | DataValue::F64(v) => write!(f, "{}", v), 246 | DataValue::U8(v) => write!(f, "{}", v), 247 | DataValue::U16(v) => write!(f, "{}", v), 248 | DataValue::U32(v) => write!(f, "{}", v), 249 | DataValue::U64(v) => write!(f, "{}", v), 250 | DataValue::I8(v) => write!(f, "{}", v), 251 | DataValue::I16(v) => write!(f, "{}", v), 252 | DataValue::I32(v) => write!(f, "{}", v), 253 | DataValue::I64(v) => write!(f, "{}", v), 254 | DataValue::Uuid(v) => write!(f, "{}", v), 255 | DataValue::UuidRand(v) => write!(f, "{}", v), 256 | DataValue::UuidSlug(v) => write!(f, "{}", v), 257 | DataValue::Utc(v) => write!(f, "{}", v.to_rfc3339()), 258 | DataValue::Text(v) => write!(f, "{}", v), 259 | DataValue::Ident(v) => write!(f, "{}", v), 260 | DataValue::Bytes(v) => { 261 | let encoded = URL_SAFE.encode(&v); 262 | write!(f, "{}", encoded) 263 | } 264 | } 265 | } 266 | } 267 | 268 | impl fmt::Display for Operator { 269 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 270 | match self { 271 | Operator::Plus => write!(f, "+"), 272 | Operator::Minus => write!(f, "-"), 273 | Operator::Multiply => write!(f, "*"), 274 | Operator::Divide => write!(f, "/"), 275 | Operator::Modulo => write!(f, "%"), 276 | Operator::Eq => write!(f, "eq"), 277 | Operator::Neq => write!(f, "neq"), 278 | Operator::Lt => write!(f, "lt"), 279 | Operator::Lte => write!(f, "lte"), 280 | Operator::Gt => write!(f, "gt"), 281 | Operator::Gte => write!(f, "gte"), 282 | Operator::And => write!(f, "&"), 283 | Operator::Or => write!(f, "|"), 284 | Operator::Like => write!(f, "like"), 285 | Operator::In => write!(f, "in"), 286 | Operator::NotIn => write!(f, "not_in"), 287 | Operator::Is => write!(f, "is"), 288 | Operator::IsNot => write!(f, "is_not"), 289 | Operator::Ilike => write!(f, "ilike"), 290 | Operator::Starts => write!(f, "starts"), 291 | } 292 | } 293 | } 294 | 295 | impl fmt::Display for DataType { 296 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 297 | let display = match self { 298 | DataType::Bool => "bool", 299 | DataType::S8 => "s8", 300 | DataType::S16 => "s16", 301 | DataType::S32 => "s32", 302 | DataType::S64 => "s64", 303 | DataType::F32 => "f32", 304 | DataType::F64 => "f64", 305 | DataType::U8 => "u8", 306 | DataType::U16 => "u16", 307 | DataType::U32 => "u32", 308 | DataType::U64 => "u64", 309 | DataType::I8 => "i8", 310 | DataType::I16 => "i16", 311 | DataType::I32 => "i32", 312 | DataType::I64 => "i64", 313 | DataType::Uuid => "uuid", 314 | DataType::UuidRand => "uuid_rand", 315 | DataType::UuidSlug => "uuid_slug", 316 | DataType::Utc => "utc", 317 | DataType::Text => "text", 318 | DataType::Ident => "ident", 319 | DataType::Url => "url", 320 | DataType::Json => "json", 321 | DataType::Bytes => "bytes", 322 | }; 323 | 324 | write!(f, "{}", display) 325 | } 326 | } 327 | 328 | impl fmt::Display for FromTable { 329 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 330 | self.from.fmt(f)?; 331 | if let Some((join_type, from_table)) = &self.join { 332 | join_type.fmt(f)?; 333 | from_table.fmt(f)?; 334 | } 335 | Ok(()) 336 | } 337 | } 338 | 339 | impl fmt::Display for TableDef { 340 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 341 | self.table.fmt(f)?; 342 | write!(f, "{{")?; 343 | for (i, col) in self.columns.iter().enumerate() { 344 | if i > 0 { 345 | write!(f, ",")?; 346 | } 347 | col.fmt(f)?; 348 | } 349 | write!(f, "}}")?; 350 | Ok(()) 351 | } 352 | } 353 | 354 | impl fmt::Display for TableName { 355 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 356 | write!(f, "{}", self.name) 357 | } 358 | } 359 | 360 | impl fmt::Display for JoinType { 361 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 362 | match self { 363 | JoinType::InnerJoin => write!(f, "-><-"), 364 | JoinType::LeftJoin => write!(f, "<-"), 365 | JoinType::RightJoin => write!(f, "->"), 366 | JoinType::FullJoin => write!(f, "<-->"), 367 | } 368 | } 369 | } 370 | -------------------------------------------------------------------------------- /src/statement/expr.rs: -------------------------------------------------------------------------------- 1 | use crate::statement::{ColumnName, Function, Operator, Value}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | //TODO: Should be able to do math operations 5 | // such as: *, +, -, /, % 6 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 7 | pub enum Expr { 8 | Column(ColumnName), 9 | Function(Function), 10 | Value(Value), 11 | MultiValue(Vec), 12 | BinaryOperation(Box), 13 | /// The expressions is explicitly 14 | /// grouped in a parenthesis 15 | Nested(Box), 16 | } 17 | 18 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 19 | pub struct ExprRename { 20 | pub expr: Expr, 21 | pub rename: Option, 22 | } 23 | 24 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 25 | pub struct BinaryOperation { 26 | pub left: Expr, 27 | pub operator: Operator, 28 | pub right: Expr, 29 | } 30 | -------------------------------------------------------------------------------- /src/statement/operator.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 4 | pub enum Operator { 5 | Plus, 6 | Minus, 7 | Multiply, 8 | Divide, 9 | Modulo, 10 | Eq, // = , eq 11 | Neq, // != , neq 12 | Lt, // <, lt 13 | Lte, // <=, lte 14 | Gt, // >, gt 15 | Gte, // >=, gte 16 | And, // AND 17 | Or, // OR 18 | Like, // LIKE, like 19 | In, // (expr) IN, in 20 | NotIn, // (expr) NOT IN, not_in 21 | Is, // (expr) IS, is 22 | IsNot, // (expr) IS NOT, is_not 23 | Ilike, // ILIKE case insensitive like, postgresql specific 24 | Starts, // Starts with, which will become ILIKE 'value%' 25 | } 26 | 27 | impl Operator { 28 | pub(crate) fn needs_separator(&self) -> bool { 29 | match self { 30 | Operator::And 31 | | Operator::Or 32 | | Operator::Plus 33 | | Operator::Minus 34 | | Operator::Multiply 35 | | Operator::Divide 36 | | Operator::Modulo => false, 37 | 38 | Operator::Eq 39 | | Operator::Neq 40 | | Operator::Lt 41 | | Operator::Lte 42 | | Operator::Gt 43 | | Operator::Gte 44 | | Operator::Like 45 | | Operator::In 46 | | Operator::NotIn 47 | | Operator::Is 48 | | Operator::IsNot 49 | | Operator::Ilike 50 | | Operator::Starts => true, 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/statement/parser.rs: -------------------------------------------------------------------------------- 1 | use crate::data_type::*; 2 | use crate::data_value; 3 | use crate::statement::column_def::*; 4 | use crate::statement::table::*; 5 | use crate::statement::*; 6 | use either::Either; 7 | use pom::parser::{call, is_a, one_of, sym, tag, Parser}; 8 | use std::iter::FromIterator; 9 | pub use utils::list_fail; 10 | use utils::*; 11 | 12 | pub mod utils; 13 | 14 | /// a valid identifier 15 | pub(crate) fn ident<'a>() -> Parser<'a, char, String> { 16 | (is_a(alpha_or_underscore) + is_a(alphanum_or_underscore).repeat(0..)) 17 | .map(|(ch1, rest_ch)| format!("{}{}", ch1, String::from_iter(rest_ch))) 18 | } 19 | 20 | pub(crate) fn table_name<'a>() -> Parser<'a, char, String> { 21 | ((strict_ident() - sym('.')).opt() + strict_ident()).map( 22 | |(schema, table)| { 23 | if let Some(schema) = schema { 24 | format!("{}.{}", schema, table) 25 | } else { 26 | table 27 | } 28 | }, 29 | ) | strict_ident() 30 | } 31 | 32 | fn restricted_ident<'a>() -> Parser<'a, char, &'a str> { 33 | tag("from") 34 | | tag("group_by") 35 | | tag("having") 36 | | tag("order_by") 37 | | tag("limit") 38 | | tag("asc") 39 | | tag("desc") 40 | | tag("page") 41 | | tag("page_size") 42 | } 43 | 44 | pub(crate) fn strict_ident<'a>() -> Parser<'a, char, String> { 45 | !(restricted_ident() - (end_or_ln() | one_of(",&=").map(|_| ()))) * ident() 46 | } 47 | 48 | /// column name can not be followed with direction: asc, desc 49 | fn column_name<'a>() -> Parser<'a, char, String> { 50 | (strict_ident() - sym('.') + strict_ident() - sym('.') + strict_ident()) 51 | .map(|((schema, table), column)| { 52 | format!("{}.{}.{}", schema, table, column) 53 | }) 54 | | (strict_ident() - sym('.') + strict_ident()) 55 | .map(|(table, column)| format!("{}.{}", table, column)) 56 | | strict_ident() 57 | | quoted_string() 58 | } 59 | 60 | pub(crate) fn column<'a>() -> Parser<'a, char, ColumnName> { 61 | column_name().map(|name| ColumnName { name }) 62 | } 63 | 64 | pub(crate) fn table<'a>() -> Parser<'a, char, TableName> { 65 | table_name().map(|name| TableName { name }) 66 | } 67 | 68 | fn bool<'a>() -> Parser<'a, char, bool> { 69 | tag("true").map(|_| true) | tag("false").map(|_| false) 70 | } 71 | 72 | fn null<'a>() -> Parser<'a, char, Value> { 73 | tag("null").map(|_| Value::Null) 74 | } 75 | 76 | pub(crate) fn value<'a>() -> Parser<'a, char, Value> { 77 | null() 78 | | bool().map(|v| Value::Bool(v)) 79 | | number().map(|n| Value::Number(n)) 80 | | quoted_string().map(|v| Value::String(v)) 81 | | single_quoted_string().map(|v| Value::String(v)) 82 | | back_quoted_string().map(|v| Value::String(v)) 83 | | (!restricted_ident() * string()).map(|s| Value::String(s)) 84 | } 85 | 86 | pub(crate) fn multi_values<'a>() -> Parser<'a, char, Vec> { 87 | sym('[') * list_fail(value(), sym(',')) - sym(']') 88 | } 89 | 90 | fn connector<'a>() -> Parser<'a, char, Operator> { 91 | tag("|").map(|_| Operator::Or) | tag("&").map(|_| Operator::And) 92 | } 93 | 94 | fn math_operator<'a>() -> Parser<'a, char, Operator> { 95 | tag("+").map(|_| Operator::Plus) 96 | | tag("-").map(|_| Operator::Minus) 97 | | tag("*").map(|_| Operator::Multiply) 98 | | tag("/").map(|_| Operator::Divide) 99 | | tag("%").map(|_| Operator::Modulo) 100 | } 101 | 102 | fn operator<'a>() -> Parser<'a, char, Operator> { 103 | tag("eq").map(|_| Operator::Eq) 104 | | tag("neq").map(|_| Operator::Neq) 105 | | tag("lte").map(|_| Operator::Lte) 106 | | tag("lt").map(|_| Operator::Lt) 107 | | tag("gte").map(|_| Operator::Gte) 108 | | tag("gt").map(|_| Operator::Gt) 109 | | tag("in").map(|_| Operator::In) 110 | | tag("not_in").map(|_| Operator::NotIn) 111 | | tag("is_not").map(|_| Operator::IsNot) 112 | | tag("like").map(|_| Operator::Like) 113 | | tag("ilike").map(|_| Operator::Ilike) 114 | | tag("starts").map(|_| Operator::Starts) 115 | | connector() 116 | | math_operator() 117 | } 118 | 119 | fn expr<'a>() -> Parser<'a, char, Expr> { 120 | (sym('(') * call(expr) - sym(')')).map(|expr| Expr::Nested(Box::new(expr))) 121 | | multi_values().map(Expr::MultiValue) 122 | | null().map(Expr::Value) 123 | | bool().map(|v| Expr::Value(Value::Bool(v))) 124 | | number().map(|v| Expr::Value(Value::Number(v))) 125 | | function().map(Expr::Function) 126 | | column().map(Expr::Column) 127 | | value().map(Expr::Value) 128 | } 129 | 130 | fn exprs_with_renames<'a>() -> Parser<'a, char, Vec> { 131 | list_fail(call(expr_rename), sym(',')) 132 | } 133 | 134 | /// column=>new_column 135 | /// 136 | fn expr_rename<'a>() -> Parser<'a, char, ExprRename> { 137 | (expr() + ((tag("=>")) * strict_ident()).opt()) 138 | .map(|(expr, rename)| ExprRename { rename, expr }) 139 | } 140 | 141 | fn expr_projection<'a>() -> Parser<'a, char, Vec> { 142 | sym('{') * exprs_with_renames() - sym('}') 143 | } 144 | 145 | fn exprs<'a>() -> Parser<'a, char, Vec> { 146 | list_fail(expr(), sym(',')) 147 | } 148 | 149 | pub(crate) fn function<'a>() -> Parser<'a, char, Function> { 150 | (strict_ident() - sym('(') - sym(')')).map(|name| Function { 151 | name, 152 | params: vec![], 153 | }) | (strict_ident() - sym('(') + call(exprs) - sym(')')) 154 | .map(|(name, params)| Function { name, params }) 155 | } 156 | 157 | #[allow(dead_code)] 158 | fn math_operation<'a>() -> Parser<'a, char, BinaryOperation> { 159 | (expr() + math_operator() + expr()).map(|((left, operator), right)| { 160 | BinaryOperation { 161 | left, 162 | operator, 163 | right, 164 | } 165 | }) 166 | } 167 | 168 | fn simple_operation<'a>() -> Parser<'a, char, BinaryOperation> { 169 | (expr() + operator() + expr()).map(|((left, operator), right)| { 170 | BinaryOperation { 171 | left, 172 | operator, 173 | right, 174 | } 175 | }) 176 | } 177 | 178 | fn simple_operation_expr<'a>() -> Parser<'a, char, Expr> { 179 | (sym('(') * call(simple_operation_expr) - sym(')')) 180 | .map(|expr| Expr::Nested(Box::new(expr))) 181 | | call(simple_operation) 182 | .map(|binop| Expr::BinaryOperation(Box::new(binop))) 183 | } 184 | 185 | fn binary_operation_expr<'a>() -> Parser<'a, char, Expr> { 186 | (sym('(') * call(binary_operation_expr) - sym(')')) 187 | .map(|expr| Expr::Nested(Box::new(expr))) 188 | | (expr() - sym('=') + (operator() - sym('.')).opt() + expr()).map( 189 | |((left, operator), right)| { 190 | Expr::BinaryOperation(Box::new(BinaryOperation { 191 | left, 192 | operator: operator.unwrap_or(Operator::Eq), 193 | right, 194 | })) 195 | }, 196 | ) 197 | | (simple_operation_expr() + connector() + call(binary_operation_expr)) 198 | .map(|((left, operator), right)| { 199 | Expr::BinaryOperation(Box::new(BinaryOperation { 200 | left, 201 | operator, 202 | right, 203 | })) 204 | }) 205 | | (expr() + connector() + call(binary_operation_expr)).map( 206 | |((left, operator), right)| { 207 | Expr::BinaryOperation(Box::new(BinaryOperation { 208 | left, 209 | operator, 210 | right, 211 | })) 212 | }, 213 | ) 214 | | call(simple_operation_expr) 215 | } 216 | 217 | fn simple_filter_expr<'a>() -> Parser<'a, char, Expr> { 218 | (sym('(') * call(simple_filter_expr) - sym(')')) 219 | .map(|expr| Expr::Nested(Box::new(expr))) 220 | | (call(binary_operation_expr) + operator() + call(simple_filter_expr)) 221 | .map(|((left, operator), right)| { 222 | Expr::BinaryOperation(Box::new(BinaryOperation { 223 | left, 224 | operator, 225 | right, 226 | })) 227 | }) 228 | | (call(binary_operation_expr) 229 | + operator() 230 | + call(binary_operation_expr)) 231 | .map(|((left, operator), right)| { 232 | Expr::BinaryOperation(Box::new(BinaryOperation { 233 | left, 234 | operator, 235 | right, 236 | })) 237 | }) 238 | | (call(binary_operation_expr) + operator() + expr()).map( 239 | |((left, operator), right)| { 240 | Expr::BinaryOperation(Box::new(BinaryOperation { 241 | left, 242 | operator, 243 | right, 244 | })) 245 | }, 246 | ) 247 | | call(binary_operation_expr) 248 | } 249 | 250 | /// parse filter as Expr 251 | pub fn filter_expr<'a>() -> Parser<'a, char, Expr> { 252 | (call(simple_filter_expr) + operator() + call(filter_expr)).map( 253 | |((left, operator), right)| { 254 | Expr::BinaryOperation(Box::new(BinaryOperation { 255 | left, 256 | operator, 257 | right, 258 | })) 259 | }, 260 | ) | call(simple_filter_expr) 261 | } 262 | 263 | fn from_table<'a>() -> Parser<'a, char, FromTable> { 264 | (table().expect("Expecting a valid table name") 265 | + (join_type() + call(from_table))) 266 | .map(|(from, (join_type, from_table))| FromTable { 267 | from, 268 | join: Some((join_type, Box::new(from_table))), 269 | }) | (table().expect("Expecting a valid table name") 270 | + (join_type() + call(from_table)).opt()) 271 | .map(|(from, join)| FromTable { 272 | from, 273 | join: join 274 | .map(|(join_type, from_table)| (join_type, Box::new(from_table))), 275 | }) 276 | } 277 | 278 | fn join_type<'a>() -> Parser<'a, char, JoinType> { 279 | tag("-><-").map(|_| JoinType::InnerJoin) 280 | | tag("<-->").map(|_| JoinType::FullJoin) 281 | | tag("<-").map(|_| JoinType::LeftJoin) 282 | | tag("->").map(|_| JoinType::RightJoin) 283 | } 284 | 285 | fn page_size<'a>() -> Parser<'a, char, i64> { 286 | (tag("page_size") - sym('=')) 287 | * integer().expect("Expecting an integer value for page_size") 288 | } 289 | 290 | /// page=2&page_size=10 291 | fn page<'a>() -> Parser<'a, char, Page> { 292 | ((tag("page") - sym('=')) 293 | * integer().expect("Expecting an integer value for page") 294 | - sym('&') 295 | + page_size().expect("must specify a page_size")) 296 | .map(|(page, page_size)| Page { page, page_size }) 297 | } 298 | 299 | fn offset<'a>() -> Parser<'a, char, i64> { 300 | (tag("offset") - sym('=')) 301 | * integer().expect("Expecting an integer value for offset") 302 | } 303 | 304 | /// limit=10&offset=20 305 | fn limit<'a>() -> Parser<'a, char, Limit> { 306 | ((tag("limit") - sym('=')) 307 | * integer().expect("Expecting an integer value for limit") 308 | + (sym('&') * offset()).opt()) 309 | .map(|(limit, offset)| Limit { limit, offset }) 310 | } 311 | 312 | fn range<'a>() -> Parser<'a, char, Range> { 313 | page().map(Range::Page) | limit().map(Range::Limit) 314 | } 315 | 316 | fn direction<'a>() -> Parser<'a, char, Direction> { 317 | tag("asc").map(|_| Direction::Asc) | tag("desc").map(|_| Direction::Desc) 318 | } 319 | 320 | /// height.asc 321 | fn order<'a>() -> Parser<'a, char, Order> { 322 | (expr() + (sym('.') * direction()).opt()) 323 | .map(|(expr, direction)| Order { expr, direction }) 324 | } 325 | 326 | /// person{name,age,class}?(age=gt.42&student=eq.true)|(gender=eq.M)&group_by=(age),grade,gender&having=min(age)=gte.42&order_by=age.desc,height.asc&page=2&page_size=10 327 | pub fn select<'a>() -> Parser<'a, char, Select> { 328 | (from_table() + expr_projection().opt() - sym('?').opt() 329 | + filter_expr().opt() 330 | + (sym('&') * tag("group_by=") * list_fail(expr(), sym(','))).opt() 331 | + (sym('&') * tag("having=") * filter_expr()).opt() 332 | + (sym('&') * tag("order_by=") * list_fail(call(order), sym(','))) 333 | .opt() 334 | + (sym('&') * range()).opt() 335 | - end_or_ln()) 336 | .map( 337 | |( 338 | ( 339 | ((((from_table, projection), filter), group_by), having), 340 | order_by, 341 | ), 342 | range, 343 | )| { 344 | Select { 345 | from_table, 346 | filter, 347 | group_by, 348 | having, 349 | projection, 350 | order_by, 351 | range, 352 | } 353 | }, 354 | ) 355 | } 356 | 357 | pub(crate) fn column_attribute<'a>() -> Parser<'a, char, ColumnAttribute> { 358 | sym('*').map(|_| ColumnAttribute::Primary) 359 | | sym('&').map(|_| ColumnAttribute::Unique) 360 | | sym('@').map(|_| ColumnAttribute::Index) 361 | } 362 | 363 | pub(crate) fn column_attributes<'a>() -> Parser<'a, char, Vec> 364 | { 365 | column_attribute().repeat(1..) 366 | } 367 | 368 | /// foreign = "(", table, ")" 369 | /// = "(", table, ["::", ",", column] ) 370 | pub(crate) fn foreign<'a>() -> Parser<'a, char, Foreign> { 371 | (sym('(') * table() + (tag("::") * column()).opt() - sym(')')) 372 | .map(|(table, column)| Foreign { table, column }) 373 | } 374 | 375 | /// parse column definition with the format 376 | /// column_def = [column_attribute], column, [foreign] [":" data_type]; 377 | /// example: 378 | /// &*product_id(product):u32 379 | pub(crate) fn column_def<'a>() -> Parser<'a, char, ColumnDef> { 380 | ((column_attributes().opt() + column() + foreign().opt() - sym(':') 381 | + data_type_def()) 382 | .map(|(((attributes, column), foreign), data_type)| ColumnDef { 383 | column, 384 | attributes, 385 | data_type_def: data_type, 386 | foreign, 387 | })) 388 | .name("column_def") 389 | } 390 | 391 | pub(crate) fn column_def_list<'a>() -> Parser<'a, char, Vec> { 392 | list_fail(column_def(), sym(',')).name("column_def_list") 393 | } 394 | 395 | pub(crate) fn enclosed_column_def_list<'a>() -> Parser<'a, char, Vec> 396 | { 397 | sym('{') * column_def_list() - sym('}') 398 | } 399 | 400 | /// example: 401 | /// product{*product_id:s32,@name:text,description:text,updated:utc,created_by(users):u32,@is_active:bool} 402 | /// 403 | /// or 404 | /// 405 | /// product(*product_id:s32,@name:text,description:text,updated:utc,created_by(users):u32,@is_active:bool) 406 | /// 407 | /// Note: that braces `{}` are invalid when used in the path part, but can be valid when used in 408 | /// query part. 409 | /// So it is safe to use the parenthesis `()` when used in actual rest api request. 410 | pub fn table_def<'a>() -> Parser<'a, char, TableDef> { 411 | (table() + enclosed_column_def_list() - end_or_ln()) 412 | .map(|(table, columns)| TableDef { table, columns }) 413 | } 414 | 415 | pub fn default_value<'a>() -> Parser<'a, char, Either> { 416 | function().map(Either::Left) | value().map(Either::Right) 417 | } 418 | 419 | /// data_type_def = data_type ["?"] "(" value ")" 420 | /// example: 421 | /// u32?(0.0) 422 | pub fn data_type_def<'a>() -> Parser<'a, char, DataTypeDef> { 423 | (data_type() 424 | + sym('?').opt() 425 | + (sym('(') * default_value() - sym(')')).opt()) 426 | .map(|((data_type, optional), default_value)| { 427 | let default = default_value.map(|d| match d { 428 | Either::Left(df) => DefaultValue::Function(df), 429 | Either::Right(dv) => DefaultValue::DataValue( 430 | data_value::cast_data_value(&dv, &data_type), 431 | ), 432 | }); 433 | 434 | DataTypeDef { 435 | data_type, 436 | is_optional: if let Some(_) = optional { true } else { false }, 437 | default, 438 | } 439 | }) 440 | .name("data_type_def") 441 | } 442 | 443 | pub fn drop_table<'a>() -> Parser<'a, char, DropTable> { 444 | (sym('-') * table() - end_or_ln()).map(|table| DropTable { table }) 445 | } 446 | 447 | fn drop_column<'a>() -> Parser<'a, char, AlterOperation> { 448 | (sym('-') * column()).map(AlterOperation::DropColumn) 449 | } 450 | 451 | fn add_column<'a>() -> Parser<'a, char, AlterOperation> { 452 | (sym('+') * column_def()).map(AlterOperation::AddColumn) 453 | } 454 | 455 | fn alter_column<'a>() -> Parser<'a, char, AlterOperation> { 456 | (column() - sym('=') + column_def()).map(|(column, column_def)| { 457 | AlterOperation::AlterColumn(column, column_def) 458 | }) 459 | } 460 | 461 | fn alter_operation<'a>() -> Parser<'a, char, AlterOperation> { 462 | alter_column() | drop_column() | add_column() 463 | } 464 | 465 | fn alter_operations<'a>() -> Parser<'a, char, Vec> { 466 | sym('{') * list_fail(alter_operation(), sym(',')) - sym('}') 467 | } 468 | 469 | pub fn alter_table<'a>() -> Parser<'a, char, AlterTable> { 470 | (table() + alter_operations() - end_or_ln()).map( 471 | |(table, alter_operations)| AlterTable { 472 | table, 473 | alter_operations, 474 | }, 475 | ) 476 | } 477 | 478 | fn returning<'a>() -> Parser<'a, char, Vec> { 479 | tag("returning=") * columns() 480 | } 481 | 482 | fn columns<'a>() -> Parser<'a, char, Vec> { 483 | list_fail(column(), sym(',')) 484 | } 485 | 486 | /// product{product_id,created_by,created,is_active}?returning=product_id,name 487 | pub fn insert<'a>() -> Parser<'a, char, Insert> { 488 | (table() - sym('{') + columns() - sym('}') + (sym('?') * returning()).opt()) 489 | .map(|((into, columns), returning)| Insert { 490 | into, 491 | columns, 492 | returning, 493 | source: Source::Values(vec![]), 494 | }) 495 | } 496 | 497 | fn column_value<'a>() -> Parser<'a, char, (ColumnName, Value)> { 498 | column() - sym('=') + value() 499 | } 500 | 501 | fn column_values<'a>() -> Parser<'a, char, Vec<(ColumnName, Value)>> { 502 | list_fail(column_value(), sym(',')) 503 | } 504 | 505 | /// product{description="I'm the new description now",is_active=false}?product_id=1 506 | pub fn update<'a>() -> Parser<'a, char, Update> { 507 | (table() - sym('{') + column_values() - sym('}') 508 | + (sym('?') * filter_expr()).opt()) 509 | .map(|((table, column_values), condition)| { 510 | let (columns, values) = column_values.into_iter().unzip(); 511 | Update { 512 | table, 513 | columns, 514 | values, 515 | condition, 516 | } 517 | }) 518 | } 519 | 520 | /// product?product_id=1 521 | pub fn delete<'a>() -> Parser<'a, char, Delete> { 522 | (table() + (sym('?') * filter_expr()).opt()) 523 | .map(|(from, condition)| Delete { from, condition }) 524 | } 525 | 526 | /// bulk delete 527 | pub fn bulk_delete<'a>() -> Parser<'a, char, BulkDelete> { 528 | (table() - sym('{') + columns() - sym('}')).map(|(from, columns)| { 529 | BulkDelete { 530 | from, 531 | columns, 532 | values: vec![], 533 | } 534 | }) 535 | } 536 | 537 | /// bulk update 538 | pub fn bulk_update<'a>() -> Parser<'a, char, BulkUpdate> { 539 | (table() - sym('{') + columns() - sym('}')).map(|(table, columns)| { 540 | BulkUpdate { 541 | table, 542 | columns, 543 | values: vec![], 544 | } 545 | }) 546 | } 547 | 548 | #[cfg(test)] 549 | mod test_private { 550 | use super::*; 551 | 552 | #[test] 553 | fn test_add_operation() { 554 | let input = to_chars("age+year"); 555 | let ret = simple_operation().parse(&input).expect("must be parsed"); 556 | println!("{:#?}", ret); 557 | assert_eq!( 558 | ret, 559 | BinaryOperation { 560 | left: Expr::Column(ColumnName { name: "age".into() }), 561 | operator: Operator::Plus, 562 | right: Expr::Column(ColumnName { 563 | name: "year".into() 564 | }), 565 | } 566 | ); 567 | } 568 | 569 | #[test] 570 | fn test_math_operation() { 571 | let input = to_chars("1+1"); 572 | let ret = math_operation().parse(&input).expect("must be parsed"); 573 | println!("{:#?}", ret); 574 | assert_eq!( 575 | ret, 576 | BinaryOperation { 577 | left: Expr::Value(Value::Number(1.0)), 578 | operator: Operator::Plus, 579 | right: Expr::Value(Value::Number(1.0)) 580 | } 581 | ); 582 | } 583 | 584 | #[test] 585 | fn test_binary_operation() { 586 | let input = to_chars("age=gt.42"); 587 | let ret = binary_operation_expr() 588 | .parse(&input) 589 | .expect("must be parsed"); 590 | println!("{:#?}", ret); 591 | assert_eq!( 592 | ret, 593 | Expr::BinaryOperation(Box::new(BinaryOperation { 594 | left: Expr::Column(ColumnName { name: "age".into() }), 595 | operator: Operator::Gt, 596 | right: Expr::Value(Value::Number(42.0)) 597 | })) 598 | ); 599 | } 600 | #[test] 601 | fn test_binary_operation_grouped() { 602 | let input = to_chars("((age=gt.42))"); 603 | let ret = binary_operation_expr() 604 | .parse(&input) 605 | .expect("must be parsed"); 606 | println!("{:#?}", ret); 607 | assert_eq!( 608 | ret, 609 | Expr::Nested(Box::new(Expr::Nested(Box::new( 610 | Expr::BinaryOperation(Box::new(BinaryOperation { 611 | left: Expr::Column(ColumnName { name: "age".into() }), 612 | operator: Operator::Gt, 613 | right: Expr::Value(Value::Number(42.0)) 614 | })) 615 | )))) 616 | ); 617 | } 618 | 619 | #[test] 620 | fn test_complex_expr() { 621 | let input = to_chars("age=gt.42|true"); 622 | let ret = filter_expr().parse(&input).expect("must be parsed"); 623 | println!("{:#?}", ret); 624 | assert_eq!( 625 | ret, 626 | Expr::BinaryOperation(Box::new(BinaryOperation { 627 | left: Expr::BinaryOperation(Box::new(BinaryOperation { 628 | left: Expr::Column(ColumnName { name: "age".into() }), 629 | operator: Operator::Gt, 630 | right: Expr::Value(Value::Number(42.0)) 631 | })), 632 | operator: Operator::Or, 633 | right: Expr::Value(Value::Bool(true)) 634 | })) 635 | ); 636 | } 637 | #[test] 638 | fn test_complex_expr_rev() { 639 | let input = to_chars("false|age=gt.42"); 640 | let ret = filter_expr().parse(&input).expect("must be parsed"); 641 | println!("{:#?}", ret); 642 | assert_eq!( 643 | ret, 644 | Expr::BinaryOperation(Box::new(BinaryOperation { 645 | left: Expr::Value(Value::Bool(false)), 646 | operator: Operator::Or, 647 | right: Expr::BinaryOperation(Box::new(BinaryOperation { 648 | left: Expr::Column(ColumnName { name: "age".into() }), 649 | operator: Operator::Gt, 650 | right: Expr::Value(Value::Number(42.0)) 651 | })), 652 | })) 653 | ); 654 | } 655 | #[test] 656 | fn test_complex_filter_grouped() { 657 | let input = to_chars("(false|age=gt.42)"); 658 | let ret = filter_expr().parse(&input).expect("must be parsed"); 659 | println!("{:#?}", ret); 660 | assert_eq!( 661 | ret, 662 | Expr::Nested(Box::new(Expr::BinaryOperation(Box::new( 663 | BinaryOperation { 664 | left: Expr::Value(Value::Bool(false)), 665 | operator: Operator::Or, 666 | right: Expr::BinaryOperation(Box::new(BinaryOperation { 667 | left: Expr::Column(ColumnName { name: "age".into() }), 668 | operator: Operator::Gt, 669 | right: Expr::Value(Value::Number(42.0)) 670 | })), 671 | } 672 | )))) 673 | ); 674 | } 675 | 676 | #[test] 677 | fn test_complex_filter_grouped_of_grouped() { 678 | let input = to_chars("(age=gt.42)|(is_active=true)"); 679 | let ret = filter_expr().parse(&input).expect("must be parsed"); 680 | println!("{:#?}", ret); 681 | assert_eq!( 682 | ret, 683 | Expr::BinaryOperation(Box::new(BinaryOperation { 684 | left: Expr::Nested(Box::new(Expr::BinaryOperation(Box::new( 685 | BinaryOperation { 686 | left: Expr::Column(ColumnName { name: "age".into() }), 687 | operator: Operator::Gt, 688 | right: Expr::Value(Value::Number(42.0)) 689 | } 690 | )))), 691 | operator: Operator::Or, 692 | right: Expr::Nested(Box::new(Expr::BinaryOperation(Box::new( 693 | BinaryOperation { 694 | left: Expr::Column(ColumnName { 695 | name: "is_active".into() 696 | }), 697 | operator: Operator::Eq, 698 | right: Expr::Value(Value::Bool(true)) 699 | } 700 | )))) 701 | })) 702 | ); 703 | } 704 | } 705 | 706 | #[cfg(test)] 707 | mod tests; 708 | -------------------------------------------------------------------------------- /src/statement/parser/utils.rs: -------------------------------------------------------------------------------- 1 | use pom::parser::*; 2 | use std::{ 3 | iter::FromIterator, 4 | str::{self, FromStr}, 5 | }; 6 | 7 | /// Parses a list with the defined separator, but will fail early when one of the 8 | /// item can not be parsed 9 | pub fn list_fail<'a, I, O, U>( 10 | parser: Parser<'a, I, O>, 11 | separator: Parser<'a, I, U>, 12 | ) -> Parser<'a, I, Vec> 13 | where 14 | O: 'a, 15 | U: 'a, 16 | { 17 | Parser::new(move |input: &'a [I], start: usize| { 18 | let mut items = vec![]; 19 | let mut pos = start; 20 | match (parser.method)(input, pos) { 21 | Ok((first_item, first_pos)) => { 22 | items.push(first_item); 23 | pos = first_pos; 24 | loop { 25 | match (separator.method)(input, pos) { 26 | Ok((_, sep_pos)) => { 27 | match (parser.method)(input, sep_pos) { 28 | Ok((more_item, more_pos)) => { 29 | items.push(more_item); 30 | pos = more_pos; 31 | } 32 | Err(e) => { 33 | // return early when there is an 34 | // error matching the succeeding 35 | // items 36 | return Err(e); 37 | } 38 | } 39 | } 40 | Err(_e) => { 41 | // the separator does not match, just break 42 | break; 43 | } 44 | } 45 | } 46 | } 47 | Err(e) => { 48 | // return early when there is an error matching the first item 49 | return Err(e); 50 | } 51 | } 52 | Ok((items, pos)) 53 | }) 54 | } 55 | 56 | pub fn to_chars(input: &str) -> Vec { 57 | input.chars().collect() 58 | } 59 | 60 | pub fn bytes_to_chars(input: &[u8]) -> Vec { 61 | let input_str = 62 | String::from_utf8(input.to_owned()).expect("must be valid utf8"); 63 | to_chars(&input_str) 64 | } 65 | 66 | pub(super) fn alpha_or_underscore(ch: char) -> bool { 67 | pom::char_class::alpha(ch as u8) || underscore(ch) 68 | } 69 | 70 | pub(super) fn alphanum_or_underscore(ch: char) -> bool { 71 | pom::char_class::alphanum(ch as u8) || underscore(ch) 72 | } 73 | 74 | pub(super) fn underscore(ch: char) -> bool { 75 | ch == '_' 76 | } 77 | 78 | pub fn end_or_ln<'a>() -> Parser<'a, char, ()> { 79 | end() | one_of("\n").discard() 80 | } 81 | 82 | /// any whitespace character 83 | pub fn space<'a>() -> Parser<'a, char, ()> { 84 | one_of(" \t\r\n").repeat(0..).discard() 85 | } 86 | 87 | /// a number including decimal 88 | pub(crate) fn number<'a>() -> Parser<'a, char, f64> { 89 | let integer = 90 | one_of("123456789") - one_of("0123456789").repeat(0..) | sym('0'); 91 | let frac = sym('.') + one_of("0123456789").repeat(1..); 92 | let exp = 93 | one_of("eE") + one_of("+-").opt() + one_of("0123456789").repeat(1..); 94 | let number = sym('-').opt() + integer + frac.opt() + exp.opt(); 95 | number 96 | .collect() 97 | .map(String::from_iter) 98 | .convert(|s| f64::from_str(&s)) 99 | } 100 | 101 | pub(crate) fn integer<'a>() -> Parser<'a, char, i64> { 102 | let int = one_of("123456789") - one_of("0123456789").repeat(0..) | sym('0'); 103 | int.collect() 104 | .map(String::from_iter) 105 | .convert(|s| i64::from_str(&s)) 106 | } 107 | 108 | /// quoted string literal 109 | pub(crate) fn quoted_string<'a>() -> Parser<'a, char, String> { 110 | let special_char = sym('\\') 111 | | sym('/') 112 | | sym('"') 113 | | sym('b').map(|_| '\x08') 114 | | sym('f').map(|_| '\x0C') 115 | | sym('n').map(|_| '\n') 116 | | sym('r').map(|_| '\r') 117 | | sym('t').map(|_| '\t'); 118 | let escape_sequence = sym('\\') * special_char; 119 | let char_string = (none_of(r#"\""#) | escape_sequence) 120 | .repeat(1..) 121 | .map(String::from_iter); 122 | let string = sym('"') * char_string.repeat(0..) - sym('"'); 123 | string.map(|strings| strings.concat()) 124 | } 125 | 126 | /// 'string' single quoted string 127 | pub(crate) fn single_quoted_string<'a>() -> Parser<'a, char, String> { 128 | let special_char = sym('\\') 129 | | sym('/') 130 | | sym('\'') 131 | | sym('b').map(|_| '\x08') 132 | | sym('f').map(|_| '\x0C') 133 | | sym('n').map(|_| '\n') 134 | | sym('r').map(|_| '\r') 135 | | sym('t').map(|_| '\t'); 136 | let escape_sequence = sym('\\') * special_char; 137 | let char_string = (none_of(r#"\'"#) | escape_sequence) 138 | .repeat(1..) 139 | .map(String::from_iter); 140 | let string = sym('\'') * char_string.repeat(0..) - sym('\''); 141 | string.map(|strings| strings.concat()) 142 | } 143 | 144 | /// `string` backquoted string since the browser don't replace this with percent encoding 145 | pub(crate) fn back_quoted_string<'a>() -> Parser<'a, char, String> { 146 | let special_char = sym('\\') 147 | | sym('/') 148 | | sym('\'') 149 | | sym('b').map(|_| '\x08') 150 | | sym('f').map(|_| '\x0C') 151 | | sym('n').map(|_| '\n') 152 | | sym('r').map(|_| '\r') 153 | | sym('t').map(|_| '\t'); 154 | let escape_sequence = sym('\\') * special_char; 155 | let char_string = (none_of(r#"\`"#) | escape_sequence) 156 | .repeat(1..) 157 | .map(String::from_iter); 158 | let string = sym('`') * char_string.repeat(0..) - sym('`'); 159 | string.map(|strings| strings.concat()) 160 | } 161 | 162 | /// string with no quote 163 | pub(crate) fn string<'a>() -> Parser<'a, char, String> { 164 | let char_string = none_of("=&()").repeat(1..).map(String::from_iter); 165 | let string = char_string.repeat(0..); 166 | string.map(|strings| strings.concat()) 167 | } 168 | -------------------------------------------------------------------------------- /src/statement/table.rs: -------------------------------------------------------------------------------- 1 | use crate::TableDef; 2 | use crate::TableName; 3 | use serde::{Deserialize, Serialize}; 4 | use std::collections::BTreeMap; 5 | use thiserror::Error; 6 | 7 | #[derive(Error, Debug)] 8 | pub enum TableError { 9 | #[error("Table join is specified, but no table lookup is supplied")] 10 | NoSuppliedTableLookup, 11 | #[error("Table: `{0}` not found in the supplied TableLookup")] 12 | TableNotFound(String), 13 | } 14 | 15 | #[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)] 16 | pub struct FromTable { 17 | pub from: TableName, 18 | pub join: Option<(JoinType, Box)>, 19 | } 20 | 21 | /// Only 3 join types is supported 22 | /// - left join 23 | /// product<-users 24 | /// - right join 25 | /// product->users 26 | /// - inner_join 27 | /// product-><-users 28 | /// 29 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 30 | pub enum JoinType { 31 | InnerJoin, 32 | LeftJoin, 33 | RightJoin, 34 | FullJoin, 35 | } 36 | 37 | #[derive(Debug)] 38 | pub struct TableLookup(BTreeMap); 39 | 40 | impl TableLookup { 41 | pub fn new() -> Self { 42 | TableLookup(BTreeMap::new()) 43 | } 44 | 45 | pub fn add_table(&mut self, table_def: TableDef) -> Option { 46 | self.0.insert(table_def.table.name.to_string(), table_def) 47 | } 48 | 49 | /// get the table definition with name 50 | pub fn get_table_def(&self, name: &str) -> Option<&TableDef> { 51 | self.0.get(name) 52 | } 53 | 54 | pub fn find_table(&self, table: &TableName) -> Option<&TableDef> { 55 | self.get_table_def(&table.name) 56 | } 57 | } 58 | 59 | #[cfg(test)] 60 | mod tests { 61 | use crate::parser::*; 62 | use crate::statement::column_def::*; 63 | use crate::statement::*; 64 | use crate::*; 65 | 66 | #[test] 67 | fn parse_drop_table() { 68 | let input = to_chars("-product"); 69 | let ret = drop_table().parse(&input).expect("must be parsed"); 70 | println!("{:#?}", ret); 71 | assert_eq!( 72 | ret, 73 | DropTable { 74 | table: TableName { 75 | name: "product".into() 76 | } 77 | } 78 | ); 79 | let statement: Statement = Into::into(ret); 80 | assert_eq!( 81 | statement.into_sql_statement(None).unwrap().to_string(), 82 | "DROP TABLE IF EXISTS product CASCADE" 83 | ); 84 | } 85 | 86 | #[test] 87 | fn parse_alter_operation() { 88 | let input = to_chars( 89 | "product{id=&*product_id:u32,-description,+discount:f32?(0.0)}", 90 | ); 91 | let ret = alter_table().parse(&input).expect("must be parsed"); 92 | println!("{:#?}", ret); 93 | 94 | assert_eq!( 95 | ret, 96 | AlterTable { 97 | table: TableName { 98 | name: "product".into() 99 | }, 100 | alter_operations: vec![ 101 | AlterOperation::AlterColumn( 102 | ColumnName { name: "id".into() }, 103 | ColumnDef { 104 | column: ColumnName { 105 | name: "product_id".into() 106 | }, 107 | attributes: Some(vec![ 108 | ColumnAttribute::Unique, 109 | ColumnAttribute::Primary, 110 | ]), 111 | data_type_def: DataTypeDef { 112 | data_type: DataType::U32, 113 | is_optional: false, 114 | default: None, 115 | }, 116 | foreign: None, 117 | }, 118 | ), 119 | AlterOperation::DropColumn(ColumnName { 120 | name: "description".into(), 121 | }), 122 | AlterOperation::AddColumn(ColumnDef { 123 | column: ColumnName { 124 | name: "discount".into() 125 | }, 126 | attributes: None, 127 | data_type_def: DataTypeDef { 128 | data_type: DataType::F32, 129 | is_optional: true, 130 | default: Some(DefaultValue::DataValue( 131 | DataValue::F32(0.0,) 132 | )), 133 | }, 134 | foreign: None, 135 | }) 136 | ], 137 | } 138 | ); 139 | } 140 | 141 | #[test] 142 | fn parse_alter_operation_simple() { 143 | let input = to_chars("product{-description,+discount:f32?(0.1)}"); 144 | let ret = alter_table().parse(&input).expect("must be parsed"); 145 | println!("{:#?}", ret); 146 | 147 | let statements = ret.into_sql_statements(None).unwrap(); 148 | assert_eq!(statements.len(), 2); 149 | assert_eq!( 150 | statements[0].to_string(), 151 | "ALTER TABLE product DROP COLUMN IF EXISTS description CASCADE" 152 | ); 153 | assert_eq!( 154 | statements[1].to_string(), 155 | "ALTER TABLE product ADD COLUMN discount FLOAT DEFAULT 0.1" 156 | ); 157 | } 158 | 159 | #[test] 160 | fn parse_data_type_def() { 161 | let input = to_chars("u32"); 162 | let ret = data_type_def().parse(&input).expect("must be parsed"); 163 | println!("{:#?}", ret); 164 | assert_eq!( 165 | ret, 166 | DataTypeDef { 167 | data_type: DataType::U32, 168 | is_optional: false, 169 | default: None, 170 | } 171 | ); 172 | } 173 | 174 | #[test] 175 | fn test_invalid_data_type_def() { 176 | let input = to_chars("invalid32"); 177 | let ret = data_type_def().parse(&input); 178 | println!("{:#?}", ret); 179 | assert!(ret.is_err()); 180 | assert!(ret 181 | .err() 182 | .unwrap() 183 | .to_string() 184 | .contains(r#"InvalidDataType("invalid32")"#)); 185 | } 186 | 187 | #[test] 188 | fn parse_column_def() { 189 | let input = to_chars("*product_id:u32"); 190 | let ret = column_def().parse(&input).expect("must be parsed"); 191 | println!("{:#?}", ret); 192 | assert_eq!( 193 | ret, 194 | ColumnDef { 195 | column: ColumnName { 196 | name: "product_id".to_string() 197 | }, 198 | attributes: Some(vec![ColumnAttribute::Primary]), 199 | data_type_def: DataTypeDef { 200 | data_type: DataType::U32, 201 | is_optional: false, 202 | default: None, 203 | }, 204 | foreign: None, 205 | }, 206 | ); 207 | } 208 | 209 | #[test] 210 | fn display_column_def() { 211 | assert_eq!( 212 | "*product_id:u32", 213 | ColumnDef { 214 | column: ColumnName { 215 | name: "product_id".to_string() 216 | }, 217 | attributes: Some(vec![ColumnAttribute::Primary]), 218 | data_type_def: DataTypeDef { 219 | data_type: DataType::U32, 220 | is_optional: false, 221 | default: None, 222 | }, 223 | foreign: None, 224 | } 225 | .to_string(), 226 | ); 227 | } 228 | 229 | #[test] 230 | fn display_column_def2() { 231 | assert_eq!( 232 | "*&@product_id(product):u32?(qr-123)", 233 | ColumnDef { 234 | column: ColumnName { 235 | name: "product_id".to_string() 236 | }, 237 | attributes: Some(vec![ 238 | ColumnAttribute::Primary, 239 | ColumnAttribute::Unique, 240 | ColumnAttribute::Index 241 | ]), 242 | data_type_def: DataTypeDef { 243 | data_type: DataType::U32, 244 | is_optional: true, 245 | default: Some(DefaultValue::DataValue(DataValue::Text( 246 | "qr-123".to_string() 247 | ))), 248 | }, 249 | foreign: Some(Foreign { 250 | table: TableName { 251 | name: "product".to_string() 252 | }, 253 | column: None 254 | }), 255 | } 256 | .to_string(), 257 | ); 258 | } 259 | 260 | #[test] 261 | fn test_column_def_list() { 262 | let input = to_chars("*product_id:u32,name:text"); 263 | let ret = column_def_list().parse(&input).expect("must be parsed"); 264 | println!("{:#?}", ret); 265 | assert_eq!( 266 | ret, 267 | vec![ 268 | ColumnDef { 269 | column: ColumnName { 270 | name: "product_id".to_string() 271 | }, 272 | attributes: Some(vec![ColumnAttribute::Primary]), 273 | data_type_def: DataTypeDef { 274 | data_type: DataType::U32, 275 | is_optional: false, 276 | default: None, 277 | }, 278 | foreign: None, 279 | }, 280 | ColumnDef { 281 | column: ColumnName { 282 | name: "name".to_string() 283 | }, 284 | attributes: None, 285 | data_type_def: DataTypeDef { 286 | data_type: DataType::Text, 287 | is_optional: false, 288 | default: None, 289 | }, 290 | foreign: None, 291 | } 292 | ], 293 | ); 294 | } 295 | 296 | #[test] 297 | fn test_invalid_column_def_list() { 298 | let input = to_chars("*product_id:invalid32,name:text"); 299 | let ret = column_def_list().parse(&input); 300 | println!("{:#?}", ret); 301 | assert!(ret.is_err()); 302 | assert!(ret 303 | .err() 304 | .unwrap() 305 | .to_string() 306 | .contains(r#"InvalidDataType("invalid32")"#)); 307 | } 308 | 309 | #[test] 310 | fn test_invalid_column_def_list_2() { 311 | let input = to_chars("id:u32,*product_id:invalid32,name:text"); 312 | let ret = column_def_list().parse(&input); 313 | println!("{:#?}", ret); 314 | assert!(ret.is_err()); 315 | assert!(ret 316 | .err() 317 | .unwrap() 318 | .to_string() 319 | .contains(r#"InvalidDataType("invalid32")"#)); 320 | } 321 | 322 | #[test] 323 | fn test_invalid_column_def_list_3() { 324 | let input = to_chars("id:u32,*product_id:u32,name:invalid_text"); 325 | let ret = column_def_list().parse(&input); 326 | println!("{:#?}", ret); 327 | assert!(ret.is_err()); 328 | assert!(ret 329 | .err() 330 | .unwrap() 331 | .to_string() 332 | .contains(r#"InvalidDataType("invalid_text")"#)); 333 | } 334 | 335 | #[test] 336 | fn parse_invalid_column_def() { 337 | let input = to_chars("*product_id:invalid32"); 338 | let ret = column_def().parse(&input); 339 | println!("{:#?}", ret); 340 | assert!(ret.is_err()); 341 | assert!(ret 342 | .err() 343 | .unwrap() 344 | .to_string() 345 | .contains(r#"InvalidDataType("invalid32")"#)); 346 | } 347 | 348 | #[test] 349 | fn parse_column_def_with_foreign() { 350 | let input = to_chars("*product_id(product):u32"); 351 | let ret = column_def().parse(&input).expect("must be parsed"); 352 | println!("{:#?}", ret); 353 | assert_eq!( 354 | ret, 355 | ColumnDef { 356 | column: ColumnName { 357 | name: "product_id".to_string() 358 | }, 359 | attributes: Some(vec![ColumnAttribute::Primary]), 360 | data_type_def: DataTypeDef { 361 | data_type: DataType::U32, 362 | is_optional: false, 363 | default: None, 364 | }, 365 | foreign: Some(Foreign { 366 | table: TableName { 367 | name: "product".to_string() 368 | }, 369 | column: None 370 | }), 371 | }, 372 | ); 373 | } 374 | 375 | #[test] 376 | fn parse_data_type_def_opt() { 377 | let input = to_chars("u32?"); 378 | let ret = data_type_def().parse(&input).expect("must be parsed"); 379 | println!("{:#?}", ret); 380 | assert_eq!( 381 | ret, 382 | DataTypeDef { 383 | data_type: DataType::U32, 384 | is_optional: true, 385 | default: None, 386 | } 387 | ); 388 | } 389 | 390 | #[test] 391 | fn parse_data_type_def_default() { 392 | let input = to_chars("f32(0.0)"); 393 | let ret = data_type_def().parse(&input).expect("must be parsed"); 394 | println!("{:#?}", ret); 395 | assert_eq!( 396 | ret, 397 | DataTypeDef { 398 | data_type: DataType::F32, 399 | is_optional: false, 400 | default: Some(DefaultValue::DataValue(DataValue::F32(0.0))), 401 | } 402 | ); 403 | } 404 | #[test] 405 | fn parse_data_type_def_opt_default() { 406 | let input = to_chars("f64?(11.62)"); 407 | let ret = data_type_def().parse(&input).expect("must be parsed"); 408 | println!("{:#?}", ret); 409 | assert_eq!( 410 | ret, 411 | DataTypeDef { 412 | data_type: DataType::F64, 413 | is_optional: true, 414 | default: Some(DefaultValue::DataValue(DataValue::F64(11.62))), 415 | } 416 | ); 417 | } 418 | 419 | #[test] 420 | fn parse_actor_table_with_invalid_data_type() { 421 | let input = to_chars( 422 | "actor{*actor_id:s32,&first_name:text,&@last_name:text,last_update:timestamp,created_by(users):u32,is_active:bool}", 423 | ); 424 | let ret = table_def().parse(&input); 425 | println!("{:#?}", ret); 426 | assert!(ret.is_err()); 427 | assert!(ret 428 | .err() 429 | .unwrap() 430 | .to_string() 431 | .contains(r#"InvalidDataType("timestamp")"#)); 432 | } 433 | 434 | #[test] 435 | fn parse_actor_table() { 436 | let input = to_chars( 437 | "actor{*actor_id:s32,&first_name:text,&@last_name:text,last_update:utc,created_by(users):u32,is_active:bool}", 438 | ); 439 | let ret = table_def().parse(&input).expect("must be parsed"); 440 | println!("{:#?}", ret); 441 | assert_eq!( 442 | ret, 443 | TableDef { 444 | table: TableName { 445 | name: "actor".into() 446 | }, 447 | columns: vec![ 448 | ColumnDef { 449 | column: ColumnName { 450 | name: "actor_id".to_string() 451 | }, 452 | attributes: Some(vec![ColumnAttribute::Primary]), 453 | data_type_def: DataTypeDef { 454 | data_type: DataType::S32, 455 | is_optional: false, 456 | default: None, 457 | }, 458 | foreign: None, 459 | }, 460 | ColumnDef { 461 | column: ColumnName { 462 | name: "first_name".into() 463 | }, 464 | attributes: Some(vec![ColumnAttribute::Unique]), 465 | data_type_def: DataTypeDef { 466 | data_type: DataType::Text, 467 | is_optional: false, 468 | default: None, 469 | }, 470 | foreign: None, 471 | }, 472 | ColumnDef { 473 | column: ColumnName { 474 | name: "last_name".into() 475 | }, 476 | attributes: Some(vec![ 477 | ColumnAttribute::Unique, 478 | ColumnAttribute::Index 479 | ]), 480 | data_type_def: DataTypeDef { 481 | data_type: DataType::Text, 482 | is_optional: false, 483 | default: None, 484 | }, 485 | foreign: None, 486 | }, 487 | ColumnDef { 488 | column: ColumnName { 489 | name: "last_update".into() 490 | }, 491 | attributes: None, 492 | data_type_def: DataTypeDef { 493 | data_type: DataType::Utc, 494 | is_optional: false, 495 | default: None, 496 | }, 497 | foreign: None, 498 | }, 499 | ColumnDef { 500 | column: ColumnName { 501 | name: "created_by".into() 502 | }, 503 | attributes: None, 504 | data_type_def: DataTypeDef { 505 | data_type: DataType::U32, 506 | is_optional: false, 507 | default: None, 508 | }, 509 | foreign: Some(Foreign { 510 | table: TableName { 511 | name: "users".into() 512 | }, 513 | column: None 514 | }), 515 | }, 516 | ColumnDef { 517 | column: ColumnName { 518 | name: "is_active".into() 519 | }, 520 | attributes: None, 521 | data_type_def: DataTypeDef { 522 | data_type: DataType::Bool, 523 | is_optional: false, 524 | default: None, 525 | }, 526 | foreign: None, 527 | }, 528 | ] 529 | } 530 | ) 531 | } 532 | 533 | #[test] 534 | fn displa_actor_table() { 535 | assert_eq!( 536 | "actor{*actor_id:s32,&first_name:text,&@last_name:text,last_update:utc,created_by(users):u32,is_active:bool}", 537 | TableDef { 538 | table: TableName { 539 | name: "actor".into() 540 | }, 541 | columns: vec![ 542 | ColumnDef { 543 | column: ColumnName { 544 | name: "actor_id".to_string() 545 | }, 546 | attributes: Some(vec![ColumnAttribute::Primary]), 547 | data_type_def: DataTypeDef { 548 | data_type: DataType::S32, 549 | is_optional: false, 550 | default: None, 551 | }, 552 | foreign: None, 553 | }, 554 | ColumnDef { 555 | column: ColumnName { 556 | name: "first_name".into() 557 | }, 558 | attributes: Some(vec![ColumnAttribute::Unique]), 559 | data_type_def: DataTypeDef { 560 | data_type: DataType::Text, 561 | is_optional: false, 562 | default: None, 563 | }, 564 | foreign: None, 565 | }, 566 | ColumnDef { 567 | column: ColumnName { 568 | name: "last_name".into() 569 | }, 570 | attributes: Some(vec![ 571 | ColumnAttribute::Unique, 572 | ColumnAttribute::Index 573 | ]), 574 | data_type_def: DataTypeDef { 575 | data_type: DataType::Text, 576 | is_optional: false, 577 | default: None, 578 | }, 579 | foreign: None, 580 | }, 581 | ColumnDef { 582 | column: ColumnName { 583 | name: "last_update".into() 584 | }, 585 | attributes: None, 586 | data_type_def: DataTypeDef { 587 | data_type: DataType::Utc, 588 | is_optional: false, 589 | default: None, 590 | }, 591 | foreign: None, 592 | }, 593 | ColumnDef { 594 | column: ColumnName { 595 | name: "created_by".into() 596 | }, 597 | attributes: None, 598 | data_type_def: DataTypeDef { 599 | data_type: DataType::U32, 600 | is_optional: false, 601 | default: None, 602 | }, 603 | foreign: Some(Foreign{table:TableName { 604 | name: "users".into() 605 | },column: None}), 606 | }, 607 | ColumnDef { 608 | column: ColumnName { 609 | name: "is_active".into() 610 | }, 611 | attributes: None, 612 | data_type_def: DataTypeDef { 613 | data_type: DataType::Bool, 614 | is_optional: false, 615 | default: None, 616 | }, 617 | foreign: None, 618 | }, 619 | ] 620 | }.to_string() 621 | ) 622 | } 623 | } 624 | -------------------------------------------------------------------------------- /src/statement/table_def.rs: -------------------------------------------------------------------------------- 1 | use crate::statement::column_def::*; 2 | use crate::statement::*; 3 | 4 | #[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize)] 5 | pub struct TableDef { 6 | pub table: TableName, 7 | pub columns: Vec, 8 | } 9 | 10 | #[derive( 11 | Debug, PartialEq, Default, Clone, Hash, Eq, Serialize, Deserialize, 12 | )] 13 | pub struct TableName { 14 | pub name: String, 15 | } 16 | 17 | impl TableDef { 18 | pub fn derive_insert(&self) -> Insert { 19 | let columns: Vec = 20 | self.columns.iter().map(|c| c.column.clone()).collect(); 21 | Insert { 22 | into: self.table.clone(), 23 | columns: columns.clone(), 24 | source: Source::Parameterized( 25 | self.columns 26 | .iter() 27 | .enumerate() 28 | .map(|(i, _c)| i + 1) 29 | .collect(), 30 | ), 31 | returning: Some(columns), 32 | } 33 | } 34 | 35 | /// find the column def for this column name matching their name 36 | pub fn find_column(&self, column: &ColumnName) -> Option<&ColumnDef> { 37 | self.columns 38 | .iter() 39 | .find(|col| col.column.name == column.name) 40 | } 41 | 42 | /// return the (local, foreign) pair to this table_name 43 | pub(crate) fn get_local_foreign_columns_pair_to_table( 44 | &self, 45 | table_name: &TableName, 46 | ) -> Vec<(&ColumnName, &ColumnName)> { 47 | self.columns 48 | .iter() 49 | .filter_map(|column| { 50 | column 51 | .foreign 52 | .as_ref() 53 | .map(|foreign| { 54 | if foreign.table == *table_name { 55 | foreign.column.as_ref().map(|foreign_column| { 56 | (&column.column, foreign_column) 57 | }) 58 | } else { 59 | None 60 | } 61 | }) 62 | .flatten() 63 | }) 64 | .collect() 65 | } 66 | 67 | /// get the primary columns of this table 68 | pub fn get_primary_columns(&self) -> Vec<&ColumnDef> { 69 | self.columns 70 | .iter() 71 | .filter(|column| match &column.attributes { 72 | Some(attributes) => attributes 73 | .iter() 74 | .any(|att| *att == ColumnAttribute::Primary), 75 | None => false, 76 | }) 77 | .collect() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/statement/value.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// coarse value from the parsing 4 | /// this is close to the json values 5 | /// 6 | /// TODO: include Integer variant 7 | #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] 8 | pub enum Value { 9 | Null, 10 | String(String), 11 | Number(f64), 12 | Bool(bool), 13 | } 14 | -------------------------------------------------------------------------------- /src/stmt_data.rs: -------------------------------------------------------------------------------- 1 | /// StmtData, this contains both statement and the data 2 | use crate::{ 3 | statement::parser::utils::bytes_to_chars, statement::Statement, CsvRows, 4 | }; 5 | use parser::parse_statement_chars; 6 | pub use parser::{parse_header, parse_select_chars}; 7 | use std::io::{BufRead, BufReader, Read}; 8 | 9 | mod parser; 10 | 11 | /// Contains both the statement commands and the data 12 | pub struct StmtData 13 | where 14 | R: Read, 15 | { 16 | pub header: Statement, 17 | pub body: BufReader, 18 | } 19 | 20 | impl StmtData 21 | where 22 | R: Read, 23 | { 24 | pub fn from_reader(reader: R) -> Result { 25 | let mut bufread = BufReader::new(reader); 26 | let mut first_line = vec![]; 27 | let _header_len = bufread.read_until(b'\n', &mut first_line)?; 28 | 29 | let header_input = bytes_to_chars(&first_line); 30 | let statement = parse_statement_chars(&header_input)?; 31 | 32 | Ok(StmtData { 33 | header: statement, 34 | body: bufread, 35 | }) 36 | } 37 | 38 | pub fn statement(&self) -> Statement { 39 | self.header.clone() 40 | } 41 | 42 | /// consume self and return as csv rows iterator 43 | pub fn rows_iter(self) -> Option> { 44 | match self.header { 45 | Statement::Select(_) => None, 46 | Statement::Delete(_) => None, 47 | Statement::AlterTable(_) => None, 48 | Statement::DropTable(_) => None, 49 | Statement::Update(_) => None, 50 | Statement::Create(_) => Some(CsvRows::new(self.body)), 51 | Statement::Insert(_) => Some(CsvRows::new(self.body)), 52 | Statement::BulkDelete(_) => Some(CsvRows::new(self.body)), 53 | Statement::BulkUpdate(_) => Some(CsvRows::new(self.body)), 54 | } 55 | } 56 | } 57 | 58 | #[cfg(test)] 59 | mod tests { 60 | use super::*; 61 | use crate::statement::Value; 62 | 63 | #[test] 64 | fn test_csv_data() { 65 | let data = "PUT /product{*product_id:s32,@name:text,description:text,updated:utc,created_by(users):u32,@is_active:bool}\n\ 66 | 1,go pro,a slightly used go pro, 2019-10-31 10:10:10\n\ 67 | 2,shovel,a slightly used shovel, 2019-11-11 11:11:11\n\ 68 | "; 69 | 70 | let csv_data = 71 | StmtData::from_reader(data.as_bytes()).expect("must be valid"); 72 | 73 | let rows: Vec> = 74 | csv_data.rows_iter().expect("must have iterator").collect(); 75 | println!("rows: {:#?}", rows); 76 | assert_eq!(rows.len(), 2); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/stmt_data/parser.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | statement::{ 3 | parser::{alter_table, drop_table, table_def}, 4 | parser::{bulk_delete, bulk_update, delete, insert, update}, 5 | parser::{utils::space, *}, 6 | Select, Statement, 7 | }, 8 | to_chars, 9 | }; 10 | use pom::parser::*; 11 | pub enum Prefix { 12 | Get, 13 | Put, 14 | Post, 15 | Patch, 16 | Delete, 17 | } 18 | 19 | pub fn parse_header(url: &str) -> Result { 20 | let url_chars = to_chars(url); 21 | parse_statement_chars(&url_chars) 22 | } 23 | 24 | pub(crate) fn parse_statement_chars( 25 | input: &[char], 26 | ) -> Result { 27 | Ok(statement_with_prefix().parse(input)?) 28 | } 29 | 30 | /// parses a typical http url into a select statement 31 | pub fn parse_select_chars(input: &[char]) -> Result { 32 | let url_parser = sym('/') * select(); 33 | Ok(url_parser.parse(input)?) 34 | } 35 | 36 | fn statement_with_prefix<'a>() -> Parser<'a, char, Statement> { 37 | (post_prefix() - space().opt() - sym('/')) 38 | * insert().map(Statement::Insert).expect("insert after POST") 39 | | (put_prefix() - space().opt() - sym('/') - sym('+').opt()) 40 | * table_def() 41 | .map(Statement::Create) 42 | .expect("create after PUT") 43 | | (delete_prefix() - space().opt() - sym('/')) 44 | * (drop_table().map(Statement::DropTable) 45 | | bulk_delete().map(Statement::BulkDelete) 46 | | delete().map(Statement::Delete)) 47 | .expect("drop table or delete after DELETE") 48 | | (patch_prefix() - space().opt() - sym('/')) 49 | * (alter_table().map(Statement::AlterTable) 50 | | update().map(Statement::Update) 51 | | bulk_update().map(Statement::BulkUpdate)) 52 | .expect("alter or update after PATCH") 53 | | (get_prefix() - space().opt() - sym('/')) 54 | * select().map(Statement::Select).expect("a select after GET") 55 | | sym('/') 56 | * select() 57 | .map(Statement::Select) 58 | .expect("only select is allowed for no prefix") 59 | } 60 | 61 | fn post_prefix<'a>() -> Parser<'a, char, Prefix> { 62 | tag("POST").map(|_| Prefix::Post) 63 | } 64 | 65 | fn delete_prefix<'a>() -> Parser<'a, char, Prefix> { 66 | tag("DELETE").map(|_| Prefix::Delete) 67 | } 68 | 69 | fn patch_prefix<'a>() -> Parser<'a, char, Prefix> { 70 | tag("PATCH").map(|_| Prefix::Patch) 71 | } 72 | 73 | fn get_prefix<'a>() -> Parser<'a, char, Prefix> { 74 | tag("GET").map(|_| Prefix::Get) 75 | } 76 | 77 | fn put_prefix<'a>() -> Parser<'a, char, Prefix> { 78 | tag("PUT").map(|_| Prefix::Put) 79 | } 80 | 81 | #[cfg(test)] 82 | mod tests { 83 | use super::*; 84 | 85 | fn parse_statement(stmt: &str) -> Result { 86 | let input = to_chars(stmt); 87 | parse_statement_chars(&input) 88 | } 89 | 90 | #[test] 91 | fn test_statement_with_method_prefix() { 92 | let select = parse_statement("GET /product").expect("must be parsed"); 93 | println!("select: {:#?}", select); 94 | match select { 95 | Statement::Select(_) => println!("ok"), 96 | _ => unreachable!(), 97 | } 98 | 99 | let select_no_prefix = 100 | parse_statement("/product").expect("must be parsed"); 101 | println!("select no prefix: {:#?}", select_no_prefix); 102 | match select_no_prefix { 103 | Statement::Select(_) => println!("ok"), 104 | _ => unreachable!(), 105 | } 106 | 107 | let insert = 108 | parse_statement("POST /product{id,name}").expect("must be parsed"); 109 | println!("insert: {:#?}", insert); 110 | match insert { 111 | Statement::Insert(_) => println!("ok"), 112 | _ => unreachable!(), 113 | } 114 | 115 | let create = parse_statement("PUT /product{id:i32,name:text}") 116 | .expect("must be parsed"); 117 | println!("create: {:#?}", create); 118 | 119 | match create { 120 | Statement::Create(_) => println!("ok"), 121 | _ => unreachable!(), 122 | } 123 | 124 | let create_plus = parse_statement("PUT /+product{id:i32,name:text}") 125 | .expect("must be parsed"); 126 | println!("create: {:#?}", create_plus); 127 | 128 | match create_plus { 129 | Statement::Create(_) => println!("ok"), 130 | _ => unreachable!(), 131 | } 132 | 133 | let delete = 134 | parse_statement("DELETE /product").expect("must be parsed"); 135 | println!("delete: {:#?}", delete); 136 | match delete { 137 | Statement::Delete(_) => println!("ok"), 138 | _ => unreachable!(), 139 | } 140 | 141 | let drop = parse_statement("DELETE /-product").expect("must be parsed"); 142 | println!("drop: {:#?}", drop); 143 | match drop { 144 | Statement::DropTable(_) => println!("ok"), 145 | _ => unreachable!(), 146 | } 147 | 148 | let update = parse_statement( 149 | "PATCH /product{name='new name',description='new desc'}", 150 | ) 151 | .expect("must be parsed"); 152 | println!("update: {:#?}", update); 153 | match update { 154 | Statement::Update(_) => println!("ok"), 155 | _ => unreachable!(), 156 | } 157 | 158 | let alter = parse_statement("PATCH /product{-name,-description}") 159 | .expect("must be parsed"); 160 | println!("alter: {:#?}", alter); 161 | match alter { 162 | Statement::AlterTable(_) => println!("ok"), 163 | _ => unreachable!(), 164 | } 165 | 166 | let alter_add_column = parse_statement( 167 | "PATCH /product{-name,-description,+discount:f32?(0.0)}", 168 | ) 169 | .expect("must be parsed"); 170 | println!("alter add column: {:#?}", alter_add_column); 171 | match alter_add_column { 172 | Statement::AlterTable(_) => println!("ok"), 173 | _ => unreachable!(), 174 | } 175 | } 176 | 177 | #[test] 178 | fn test_statement_with_method_prefix_no_space() { 179 | let select = parse_statement("GET/product").expect("must be parsed"); 180 | println!("select: {:#?}", select); 181 | match select { 182 | Statement::Select(_) => println!("ok"), 183 | _ => unreachable!(), 184 | } 185 | 186 | let select_no_prefix = 187 | parse_statement("/product").expect("must be parsed"); 188 | println!("select no prefix: {:#?}", select_no_prefix); 189 | match select_no_prefix { 190 | Statement::Select(_) => println!("ok"), 191 | _ => unreachable!(), 192 | } 193 | 194 | let insert = 195 | parse_statement("POST/product{id,name}").expect("must be parsed"); 196 | println!("insert: {:#?}", insert); 197 | match insert { 198 | Statement::Insert(_) => println!("ok"), 199 | _ => unreachable!(), 200 | } 201 | 202 | let create = parse_statement("PUT/product{id:i32,name:text}") 203 | .expect("must be parsed"); 204 | println!("create: {:#?}", create); 205 | 206 | match create { 207 | Statement::Create(_) => println!("ok"), 208 | _ => unreachable!(), 209 | } 210 | 211 | let create_plus = parse_statement("PUT/+product{id:i32,name:text}") 212 | .expect("must be parsed"); 213 | println!("create: {:#?}", create_plus); 214 | 215 | match create_plus { 216 | Statement::Create(_) => println!("ok"), 217 | _ => unreachable!(), 218 | } 219 | 220 | let delete = parse_statement("DELETE/product").expect("must be parsed"); 221 | println!("delete: {:#?}", delete); 222 | match delete { 223 | Statement::Delete(_) => println!("ok"), 224 | _ => unreachable!(), 225 | } 226 | 227 | let drop = parse_statement("DELETE/-product").expect("must be parsed"); 228 | println!("drop: {:#?}", drop); 229 | match drop { 230 | Statement::DropTable(_) => println!("ok"), 231 | _ => unreachable!(), 232 | } 233 | 234 | let update = parse_statement( 235 | "PATCH/product{name='new name',description='new desc'}", 236 | ) 237 | .expect("must be parsed"); 238 | println!("update: {:#?}", update); 239 | match update { 240 | Statement::Update(_) => println!("ok"), 241 | _ => unreachable!(), 242 | } 243 | 244 | let alter = parse_statement("PATCH/product{-name,-description}") 245 | .expect("must be parsed"); 246 | println!("alter: {:#?}", alter); 247 | match alter { 248 | Statement::AlterTable(_) => println!("ok"), 249 | _ => unreachable!(), 250 | } 251 | 252 | let alter_add_column = parse_statement( 253 | "PATCH/product{-name,-description,+discount:f32?(0.0)}", 254 | ) 255 | .expect("must be parsed"); 256 | println!("alter add column: {:#?}", alter_add_column); 257 | match alter_add_column { 258 | Statement::AlterTable(_) => println!("ok"), 259 | _ => unreachable!(), 260 | } 261 | } 262 | 263 | #[test] 264 | fn test_select_and_paging() { 265 | let select = parse_statement("/product&page=1&page_size=20") 266 | .expect("must be parsed"); 267 | println!("select: {:#?}", select); 268 | match select { 269 | Statement::Select(_) => println!("ok"), 270 | _ => unreachable!(), 271 | } 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | cargo test --all --all-features 2 | -------------------------------------------------------------------------------- /tests/left_join.rs: -------------------------------------------------------------------------------- 1 | use restq::{ 2 | statement::{column_def::*, *}, 3 | *, 4 | }; 5 | 6 | #[test] 7 | fn multiple_left_joins() { 8 | let input = "bazaar.review<-bazaar.product_review<-bazaar.product{bazaar.review.review_id}?bazaar.product.product_id=eq.'3c03c6f0-7d91-4570-a882-0ef44c427b90'&page=1&page_size=40"; 9 | println!("{}", input); 10 | let input_chars = to_chars(input); 11 | let ret = select().parse(&input_chars).expect("must be parsed"); 12 | println!("ret: {:#?}", ret); 13 | let mut table_lookup = TableLookup::new(); 14 | 15 | let product = TableDef { 16 | table: TableName { 17 | name: "bazaar.product".into(), 18 | }, 19 | columns: vec![ 20 | ColumnDef { 21 | column: ColumnName { 22 | name: "organization_id".into(), 23 | }, 24 | attributes: None, 25 | data_type_def: DataTypeDef { 26 | data_type: DataType::Uuid, 27 | is_optional: true, 28 | default: None, 29 | }, 30 | foreign: None, 31 | }, 32 | ColumnDef { 33 | column: ColumnName { 34 | name: "client_id".into(), 35 | }, 36 | attributes: None, 37 | data_type_def: DataTypeDef { 38 | data_type: DataType::Uuid, 39 | is_optional: true, 40 | default: None, 41 | }, 42 | foreign: None, 43 | }, 44 | ColumnDef { 45 | column: ColumnName { 46 | name: "created".into(), 47 | }, 48 | attributes: None, 49 | data_type_def: DataTypeDef { 50 | data_type: DataType::Utc, 51 | is_optional: false, 52 | default: Some(DefaultValue::Function(Function { 53 | name: "now".into(), 54 | params: vec![], 55 | })), 56 | }, 57 | foreign: None, 58 | }, 59 | ColumnDef { 60 | column: ColumnName { 61 | name: "created_by".into(), 62 | }, 63 | attributes: None, 64 | data_type_def: DataTypeDef { 65 | data_type: DataType::Uuid, 66 | is_optional: true, 67 | default: None, 68 | }, 69 | foreign: None, 70 | }, 71 | ColumnDef { 72 | column: ColumnName { 73 | name: "updated".into(), 74 | }, 75 | attributes: None, 76 | data_type_def: DataTypeDef { 77 | data_type: DataType::Utc, 78 | is_optional: false, 79 | default: Some(DefaultValue::Function(Function { 80 | name: "now".into(), 81 | params: vec![], 82 | })), 83 | }, 84 | foreign: None, 85 | }, 86 | ColumnDef { 87 | column: ColumnName { 88 | name: "updated_by".into(), 89 | }, 90 | attributes: None, 91 | data_type_def: DataTypeDef { 92 | data_type: DataType::Uuid, 93 | is_optional: true, 94 | default: None, 95 | }, 96 | foreign: None, 97 | }, 98 | ColumnDef { 99 | column: ColumnName { 100 | name: "priority".into(), 101 | }, 102 | attributes: None, 103 | data_type_def: DataTypeDef { 104 | data_type: DataType::F64, 105 | is_optional: true, 106 | default: None, 107 | }, 108 | foreign: None, 109 | }, 110 | ColumnDef { 111 | column: ColumnName { 112 | name: "name".into(), 113 | }, 114 | attributes: None, 115 | data_type_def: DataTypeDef { 116 | data_type: DataType::Text, 117 | is_optional: true, 118 | default: None, 119 | }, 120 | foreign: None, 121 | }, 122 | ColumnDef { 123 | column: ColumnName { 124 | name: "description".into(), 125 | }, 126 | attributes: None, 127 | data_type_def: DataTypeDef { 128 | data_type: DataType::Text, 129 | is_optional: true, 130 | default: None, 131 | }, 132 | foreign: None, 133 | }, 134 | ColumnDef { 135 | column: ColumnName { 136 | name: "help".into(), 137 | }, 138 | attributes: None, 139 | data_type_def: DataTypeDef { 140 | data_type: DataType::Text, 141 | is_optional: true, 142 | default: None, 143 | }, 144 | foreign: None, 145 | }, 146 | ColumnDef { 147 | column: ColumnName { 148 | name: "active".into(), 149 | }, 150 | attributes: None, 151 | data_type_def: DataTypeDef { 152 | data_type: DataType::Bool, 153 | is_optional: false, 154 | default: Some(DefaultValue::DataValue(DataValue::Bool( 155 | true, 156 | ))), 157 | }, 158 | foreign: None, 159 | }, 160 | ColumnDef { 161 | column: ColumnName { 162 | name: "product_id".into(), 163 | }, 164 | attributes: Some(vec![ColumnAttribute::Primary]), 165 | data_type_def: DataTypeDef { 166 | data_type: DataType::Uuid, 167 | is_optional: false, 168 | default: Some(DefaultValue::Function(Function { 169 | name: "uuid_generate_v4".into(), 170 | params: vec![], 171 | })), 172 | }, 173 | foreign: None, 174 | }, 175 | ColumnDef { 176 | column: ColumnName { 177 | name: "parent_product_id".into(), 178 | }, 179 | attributes: None, 180 | data_type_def: DataTypeDef { 181 | data_type: DataType::Uuid, 182 | is_optional: true, 183 | default: None, 184 | }, 185 | foreign: None, 186 | }, 187 | ColumnDef { 188 | column: ColumnName { 189 | name: "is_service".into(), 190 | }, 191 | attributes: None, 192 | data_type_def: DataTypeDef { 193 | data_type: DataType::Bool, 194 | is_optional: true, 195 | default: Some(DefaultValue::DataValue(DataValue::Bool( 196 | false, 197 | ))), 198 | }, 199 | foreign: None, 200 | }, 201 | ColumnDef { 202 | column: ColumnName { 203 | name: "price".into(), 204 | }, 205 | attributes: None, 206 | data_type_def: DataTypeDef { 207 | data_type: DataType::F64, 208 | is_optional: true, 209 | default: None, 210 | }, 211 | foreign: None, 212 | }, 213 | ColumnDef { 214 | column: ColumnName { 215 | name: "use_parent_price".into(), 216 | }, 217 | attributes: None, 218 | data_type_def: DataTypeDef { 219 | data_type: DataType::Bool, 220 | is_optional: true, 221 | default: Some(DefaultValue::DataValue(DataValue::Bool( 222 | false, 223 | ))), 224 | }, 225 | foreign: None, 226 | }, 227 | ColumnDef { 228 | column: ColumnName { 229 | name: "unit".into(), 230 | }, 231 | attributes: None, 232 | data_type_def: DataTypeDef { 233 | data_type: DataType::Text, 234 | is_optional: true, 235 | default: None, 236 | }, 237 | foreign: None, 238 | }, 239 | ColumnDef { 240 | column: ColumnName { 241 | name: "tags".into(), 242 | }, 243 | attributes: None, 244 | data_type_def: DataTypeDef { 245 | data_type: DataType::Text, 246 | is_optional: true, 247 | default: None, 248 | }, 249 | foreign: None, 250 | }, 251 | ColumnDef { 252 | column: ColumnName { 253 | name: "info".into(), 254 | }, 255 | attributes: None, 256 | data_type_def: DataTypeDef { 257 | data_type: DataType::Text, 258 | is_optional: true, 259 | default: None, 260 | }, 261 | foreign: None, 262 | }, 263 | ColumnDef { 264 | column: ColumnName { 265 | name: "seq_no".into(), 266 | }, 267 | attributes: None, 268 | data_type_def: DataTypeDef { 269 | data_type: DataType::I32, 270 | is_optional: true, 271 | default: None, 272 | }, 273 | foreign: None, 274 | }, 275 | ColumnDef { 276 | column: ColumnName { 277 | name: "upfront_fee".into(), 278 | }, 279 | attributes: None, 280 | data_type_def: DataTypeDef { 281 | data_type: DataType::F64, 282 | is_optional: true, 283 | default: Some(DefaultValue::DataValue(DataValue::F64(0.0))), 284 | }, 285 | foreign: None, 286 | }, 287 | ColumnDef { 288 | column: ColumnName { 289 | name: "barcode".into(), 290 | }, 291 | attributes: None, 292 | data_type_def: DataTypeDef { 293 | data_type: DataType::Text, 294 | is_optional: true, 295 | default: None, 296 | }, 297 | foreign: None, 298 | }, 299 | ColumnDef { 300 | column: ColumnName { 301 | name: "owner_id".into(), 302 | }, 303 | attributes: None, 304 | data_type_def: DataTypeDef { 305 | data_type: DataType::Uuid, 306 | is_optional: true, 307 | default: None, 308 | }, 309 | foreign: Some(Foreign { 310 | table: TableName { 311 | name: "bazaar.users".into(), 312 | }, 313 | column: Some(ColumnName { 314 | name: "user_id".into(), 315 | }), 316 | }), 317 | }, 318 | ColumnDef { 319 | column: ColumnName { 320 | name: "currency_id".into(), 321 | }, 322 | attributes: None, 323 | data_type_def: DataTypeDef { 324 | data_type: DataType::Uuid, 325 | is_optional: true, 326 | default: None, 327 | }, 328 | foreign: Some(Foreign { 329 | table: TableName { 330 | name: "payment.currency".into(), 331 | }, 332 | column: Some(ColumnName { 333 | name: "currency_id".into(), 334 | }), 335 | }), 336 | }, 337 | ], 338 | }; 339 | 340 | let product_review = TableDef { 341 | table: TableName { 342 | name: "bazaar.product_review".into(), 343 | }, 344 | columns: vec![ 345 | ColumnDef { 346 | column: ColumnName { 347 | name: "organization_id".into(), 348 | }, 349 | attributes: None, 350 | data_type_def: DataTypeDef { 351 | data_type: DataType::Uuid, 352 | is_optional: true, 353 | default: None, 354 | }, 355 | foreign: None, 356 | }, 357 | ColumnDef { 358 | column: ColumnName { 359 | name: "client_id".into(), 360 | }, 361 | attributes: None, 362 | data_type_def: DataTypeDef { 363 | data_type: DataType::Uuid, 364 | is_optional: true, 365 | default: None, 366 | }, 367 | foreign: None, 368 | }, 369 | ColumnDef { 370 | column: ColumnName { 371 | name: "created".into(), 372 | }, 373 | attributes: None, 374 | data_type_def: DataTypeDef { 375 | data_type: DataType::Utc, 376 | is_optional: false, 377 | default: Some(DefaultValue::Function(Function { 378 | name: "now".into(), 379 | params: vec![], 380 | })), 381 | }, 382 | foreign: None, 383 | }, 384 | ColumnDef { 385 | column: ColumnName { 386 | name: "created_by".into(), 387 | }, 388 | attributes: None, 389 | data_type_def: DataTypeDef { 390 | data_type: DataType::Uuid, 391 | is_optional: true, 392 | default: None, 393 | }, 394 | foreign: None, 395 | }, 396 | ColumnDef { 397 | column: ColumnName { 398 | name: "updated".into(), 399 | }, 400 | attributes: None, 401 | data_type_def: DataTypeDef { 402 | data_type: DataType::Utc, 403 | is_optional: false, 404 | default: Some(DefaultValue::Function(Function { 405 | name: "now".into(), 406 | params: vec![], 407 | })), 408 | }, 409 | foreign: None, 410 | }, 411 | ColumnDef { 412 | column: ColumnName { 413 | name: "updated_by".into(), 414 | }, 415 | attributes: None, 416 | data_type_def: DataTypeDef { 417 | data_type: DataType::Uuid, 418 | is_optional: true, 419 | default: None, 420 | }, 421 | foreign: None, 422 | }, 423 | ColumnDef { 424 | column: ColumnName { 425 | name: "priority".into(), 426 | }, 427 | attributes: None, 428 | data_type_def: DataTypeDef { 429 | data_type: DataType::F64, 430 | is_optional: true, 431 | default: None, 432 | }, 433 | foreign: None, 434 | }, 435 | ColumnDef { 436 | column: ColumnName { 437 | name: "product_id".into(), 438 | }, 439 | attributes: Some(vec![ColumnAttribute::Primary]), 440 | data_type_def: DataTypeDef { 441 | data_type: DataType::Uuid, 442 | is_optional: false, 443 | default: None, 444 | }, 445 | foreign: Some(Foreign { 446 | table: TableName { 447 | name: "bazaar.product".into(), 448 | }, 449 | column: Some(ColumnName { 450 | name: "product_id".into(), 451 | }), 452 | }), 453 | }, 454 | ColumnDef { 455 | column: ColumnName { 456 | name: "review_id".into(), 457 | }, 458 | attributes: Some(vec![ColumnAttribute::Primary]), 459 | data_type_def: DataTypeDef { 460 | data_type: DataType::Uuid, 461 | is_optional: false, 462 | default: None, 463 | }, 464 | foreign: Some(Foreign { 465 | table: TableName { 466 | name: "bazaar.review".into(), 467 | }, 468 | column: Some(ColumnName { 469 | name: "review_id".into(), 470 | }), 471 | }), 472 | }, 473 | ], 474 | }; 475 | 476 | let review = TableDef { 477 | table: TableName { 478 | name: "bazaar.review".into(), 479 | }, 480 | columns: vec![ 481 | ColumnDef { 482 | column: ColumnName { 483 | name: "organization_id".into(), 484 | }, 485 | attributes: None, 486 | data_type_def: DataTypeDef { 487 | data_type: DataType::Uuid, 488 | is_optional: true, 489 | default: None, 490 | }, 491 | foreign: None, 492 | }, 493 | ColumnDef { 494 | column: ColumnName { 495 | name: "client_id".into(), 496 | }, 497 | attributes: None, 498 | data_type_def: DataTypeDef { 499 | data_type: DataType::Uuid, 500 | is_optional: true, 501 | default: None, 502 | }, 503 | foreign: None, 504 | }, 505 | ColumnDef { 506 | column: ColumnName { 507 | name: "created".into(), 508 | }, 509 | attributes: None, 510 | data_type_def: DataTypeDef { 511 | data_type: DataType::Utc, 512 | is_optional: false, 513 | default: Some(DefaultValue::Function(Function { 514 | name: "now".into(), 515 | params: vec![], 516 | })), 517 | }, 518 | foreign: None, 519 | }, 520 | ColumnDef { 521 | column: ColumnName { 522 | name: "created_by".into(), 523 | }, 524 | attributes: None, 525 | data_type_def: DataTypeDef { 526 | data_type: DataType::Uuid, 527 | is_optional: true, 528 | default: None, 529 | }, 530 | foreign: None, 531 | }, 532 | ColumnDef { 533 | column: ColumnName { 534 | name: "updated".into(), 535 | }, 536 | attributes: None, 537 | data_type_def: DataTypeDef { 538 | data_type: DataType::Utc, 539 | is_optional: false, 540 | default: Some(DefaultValue::Function(Function { 541 | name: "now".into(), 542 | params: vec![], 543 | })), 544 | }, 545 | foreign: None, 546 | }, 547 | ColumnDef { 548 | column: ColumnName { 549 | name: "updated_by".into(), 550 | }, 551 | attributes: None, 552 | data_type_def: DataTypeDef { 553 | data_type: DataType::Uuid, 554 | is_optional: true, 555 | default: None, 556 | }, 557 | foreign: None, 558 | }, 559 | ColumnDef { 560 | column: ColumnName { 561 | name: "priority".into(), 562 | }, 563 | attributes: None, 564 | data_type_def: DataTypeDef { 565 | data_type: DataType::F64, 566 | is_optional: true, 567 | default: None, 568 | }, 569 | foreign: None, 570 | }, 571 | ColumnDef { 572 | column: ColumnName { 573 | name: "name".into(), 574 | }, 575 | attributes: None, 576 | data_type_def: DataTypeDef { 577 | data_type: DataType::Text, 578 | is_optional: true, 579 | default: None, 580 | }, 581 | foreign: None, 582 | }, 583 | ColumnDef { 584 | column: ColumnName { 585 | name: "description".into(), 586 | }, 587 | attributes: None, 588 | data_type_def: DataTypeDef { 589 | data_type: DataType::Text, 590 | is_optional: true, 591 | default: None, 592 | }, 593 | foreign: None, 594 | }, 595 | ColumnDef { 596 | column: ColumnName { 597 | name: "help".into(), 598 | }, 599 | attributes: None, 600 | data_type_def: DataTypeDef { 601 | data_type: DataType::Text, 602 | is_optional: true, 603 | default: None, 604 | }, 605 | foreign: None, 606 | }, 607 | ColumnDef { 608 | column: ColumnName { 609 | name: "active".into(), 610 | }, 611 | attributes: None, 612 | data_type_def: DataTypeDef { 613 | data_type: DataType::Bool, 614 | is_optional: false, 615 | default: Some(DefaultValue::DataValue(DataValue::Bool( 616 | true, 617 | ))), 618 | }, 619 | foreign: None, 620 | }, 621 | ColumnDef { 622 | column: ColumnName { 623 | name: "rating".into(), 624 | }, 625 | attributes: None, 626 | data_type_def: DataTypeDef { 627 | data_type: DataType::I32, 628 | is_optional: true, 629 | default: None, 630 | }, 631 | foreign: None, 632 | }, 633 | ColumnDef { 634 | column: ColumnName { 635 | name: "comment".into(), 636 | }, 637 | attributes: None, 638 | data_type_def: DataTypeDef { 639 | data_type: DataType::Text, 640 | is_optional: true, 641 | default: None, 642 | }, 643 | foreign: None, 644 | }, 645 | ColumnDef { 646 | column: ColumnName { 647 | name: "review_id".into(), 648 | }, 649 | attributes: Some(vec![ColumnAttribute::Primary]), 650 | data_type_def: DataTypeDef { 651 | data_type: DataType::Uuid, 652 | is_optional: false, 653 | default: None, 654 | }, 655 | foreign: None, 656 | }, 657 | ColumnDef { 658 | column: ColumnName { 659 | name: "user_id".into(), 660 | }, 661 | attributes: None, 662 | data_type_def: DataTypeDef { 663 | data_type: DataType::Uuid, 664 | is_optional: true, 665 | default: None, 666 | }, 667 | foreign: Some(Foreign { 668 | table: TableName { 669 | name: "bazaar.users".into(), 670 | }, 671 | column: Some(ColumnName { 672 | name: "user_id".into(), 673 | }), 674 | }), 675 | }, 676 | ColumnDef { 677 | column: ColumnName { 678 | name: "approved".into(), 679 | }, 680 | attributes: None, 681 | data_type_def: DataTypeDef { 682 | data_type: DataType::Bool, 683 | is_optional: true, 684 | default: None, 685 | }, 686 | foreign: None, 687 | }, 688 | ColumnDef { 689 | column: ColumnName { 690 | name: "approvedby".into(), 691 | }, 692 | attributes: None, 693 | data_type_def: DataTypeDef { 694 | data_type: DataType::Uuid, 695 | is_optional: true, 696 | default: None, 697 | }, 698 | foreign: None, 699 | }, 700 | ], 701 | }; 702 | table_lookup.add_table(product); 703 | table_lookup.add_table(product_review); 704 | table_lookup.add_table(review); 705 | 706 | let sql = ret 707 | .into_sql_statement(Some(&table_lookup)) 708 | .expect("must not error"); 709 | println!("sql: {}", sql.to_string()); 710 | assert_eq!(input, ret.to_string()); 711 | assert_eq!("SELECT bazaar.review.review_id FROM bazaar.review LEFT JOIN bazaar.product_review ON bazaar.product_review.review_id = bazaar.review.review_id LEFT JOIN bazaar.product ON bazaar.product_review.product_id = bazaar.product.product_id WHERE bazaar.product.product_id = '3c03c6f0-7d91-4570-a882-0ef44c427b90' LIMIT 40 OFFSET 0", sql.to_string()); 712 | } 713 | -------------------------------------------------------------------------------- /tests/select.rs: -------------------------------------------------------------------------------- 1 | use restq::*; 2 | 3 | #[test] 4 | fn complex_select() { 5 | let input = "person?age=lt.42&(student=eq.true|gender=eq.'M')&group_by=sum(age),grade,gender&having=min(age)=gt.42&order_by=age.desc,height.asc&page=20&page_size=100"; 6 | let input_chars = to_chars(input); 7 | let ret = select().parse(&input_chars).expect("must be parsed"); 8 | println!("ret: {:#?}", ret); 9 | assert_eq!(input, ret.to_string()); 10 | } 11 | 12 | #[test] 13 | fn select_with_uuid() { 14 | let input = 15 | "bazaar.product?product_id=eq.'3c03c6f0-7d91-4570-a882-0ef44c427b90'"; 16 | let input_chars = to_chars(input); 17 | let ret = select().parse(&input_chars).expect("must be parsed"); 18 | println!("ret: {:#?}", ret); 19 | assert_eq!(input, ret.to_string()); 20 | } 21 | 22 | #[test] 23 | fn complex_select_with_projection() { 24 | let input = "person{person_id,name,updated}?age=lt.42&(student=eq.true|gender=eq.'M')&group_by=sum(age),grade,gender&having=min(age)=gt.42&order_by=age.desc,height.asc&page=20&page_size=100"; 25 | let input_chars = to_chars(input); 26 | let ret = select().parse(&input_chars).expect("must be parsed"); 27 | println!("ret: {:#?}", ret); 28 | assert_eq!(input, ret.to_string()); 29 | } 30 | 31 | #[test] 32 | fn complex_select_with_filter_in() { 33 | let input = "person{person_id,name,updated}?person_id=in.[100,101,102]"; 34 | let input_chars = to_chars(input); 35 | let ret = select().parse(&input_chars).expect("must be parsed"); 36 | println!("ret: {:#?}", ret); 37 | assert_eq!(input, ret.to_string()); 38 | } 39 | 40 | #[test] 41 | fn select_with_left_join() { 42 | let input = "public.inventory<-public.film{inventory_id,film_id,store_id,last_update}?public.film.film_id=eq.1&page=1&page_size=40"; 43 | let input_chars = to_chars(input); 44 | let ret = select().parse(&input_chars).expect("must be parsed"); 45 | println!("ret: {:#?}", ret); 46 | assert_eq!(input, ret.to_string()); 47 | } 48 | -------------------------------------------------------------------------------- /tests/test_table_def.rs: -------------------------------------------------------------------------------- 1 | use restq::{ 2 | statement::parser::*, 3 | statement::{column_def::*, *}, 4 | *, 5 | }; 6 | 7 | #[test] 8 | fn more_complex_table_def() { 9 | let input = r#"adempiere.ad_element{*ad_element_id:f64,ad_client_id:f64,ad_org_id:f64,isactive:text('Y'),created:utc,createdby:f64,updated:utc(now()),updatedby:f64,columnname:text,entitytype(adempiere.ad_entitytype):text('D'),name:text,printname:text,description:text?,help:text?,po_name:text?,po_printname:text?,po_description:text?,po_help:text?,ad_reference_id(adempiere.ad_reference):f64?,fieldlength:f64?,ad_reference_value_id(adempiere.ad_reference):f64?,uuid:text?(NULL)}"#; 10 | let input_chars = to_chars(input); 11 | let ret = table_def().parse(&input_chars).expect("must be parsed"); 12 | println!("ret: {:#?}", ret); 13 | } 14 | 15 | #[test] 16 | fn complex_table_def() { 17 | let input = r#"public.film{*film_id:s32,title:text,description:text?,release_year:s16?,language_id(public.language::language_id):s16,original_language_id(public.language):s16?,rental_duration:s16(3),rental_rate:f64(4.99),length:s16?,replacement_cost:f64(19.99),rating:text?("'G'::mpaa_rating"),last_update:utc,special_features:text?,fulltext:text}"#; 18 | let input_chars = to_chars(input); 19 | let ret = table_def().parse(&input_chars).expect("must be parsed"); 20 | println!("ret: {:#?}", ret); 21 | assert_eq!( 22 | ret, 23 | TableDef { 24 | table: TableName { 25 | name: "public.film".to_string(), 26 | }, 27 | columns: vec![ 28 | ColumnDef { 29 | column: ColumnName { 30 | name: "film_id".to_string() 31 | }, 32 | attributes: Some(vec![ColumnAttribute::Primary,],), 33 | data_type_def: DataTypeDef { 34 | data_type: DataType::S32, 35 | is_optional: false, 36 | default: None, 37 | }, 38 | foreign: None, 39 | }, 40 | ColumnDef { 41 | column: ColumnName { 42 | name: "title".to_string() 43 | }, 44 | attributes: None, 45 | data_type_def: DataTypeDef { 46 | data_type: DataType::Text, 47 | is_optional: false, 48 | default: None, 49 | }, 50 | foreign: None, 51 | }, 52 | ColumnDef { 53 | column: ColumnName { 54 | name: "description".to_string(), 55 | }, 56 | attributes: None, 57 | data_type_def: DataTypeDef { 58 | data_type: DataType::Text, 59 | is_optional: true, 60 | default: None, 61 | }, 62 | foreign: None, 63 | }, 64 | ColumnDef { 65 | column: ColumnName { 66 | name: "release_year".to_string(), 67 | }, 68 | attributes: None, 69 | data_type_def: DataTypeDef { 70 | data_type: DataType::S16, 71 | is_optional: true, 72 | default: None, 73 | }, 74 | foreign: None, 75 | }, 76 | ColumnDef { 77 | column: ColumnName { 78 | name: "language_id".to_string(), 79 | }, 80 | attributes: None, 81 | data_type_def: DataTypeDef { 82 | data_type: DataType::S16, 83 | is_optional: false, 84 | default: None, 85 | }, 86 | foreign: Some(Foreign { 87 | table: TableName { 88 | name: "public.language".to_string(), 89 | }, 90 | column: Some(ColumnName { 91 | name: "language_id".to_string() 92 | }) 93 | },), 94 | }, 95 | ColumnDef { 96 | column: ColumnName { 97 | name: "original_language_id".to_string(), 98 | }, 99 | attributes: None, 100 | data_type_def: DataTypeDef { 101 | data_type: DataType::S16, 102 | is_optional: true, 103 | default: None, 104 | }, 105 | foreign: Some(Foreign { 106 | table: TableName { 107 | name: "public.language".to_string(), 108 | }, 109 | column: None 110 | },), 111 | }, 112 | ColumnDef { 113 | column: ColumnName { 114 | name: "rental_duration".to_string(), 115 | }, 116 | attributes: None, 117 | data_type_def: DataTypeDef { 118 | data_type: DataType::S16, 119 | is_optional: false, 120 | default: Some(DefaultValue::DataValue(DataValue::S16( 121 | 3, 122 | ))), 123 | }, 124 | foreign: None, 125 | }, 126 | ColumnDef { 127 | column: ColumnName { 128 | name: "rental_rate".to_string(), 129 | }, 130 | attributes: None, 131 | data_type_def: DataTypeDef { 132 | data_type: DataType::F64, 133 | is_optional: false, 134 | default: Some(DefaultValue::DataValue(DataValue::F64( 135 | 4.99, 136 | ))), 137 | }, 138 | foreign: None, 139 | }, 140 | ColumnDef { 141 | column: ColumnName { 142 | name: "length".to_string() 143 | }, 144 | attributes: None, 145 | data_type_def: DataTypeDef { 146 | data_type: DataType::S16, 147 | is_optional: true, 148 | default: None, 149 | }, 150 | foreign: None, 151 | }, 152 | ColumnDef { 153 | column: ColumnName { 154 | name: "replacement_cost".to_string(), 155 | }, 156 | attributes: None, 157 | data_type_def: DataTypeDef { 158 | data_type: DataType::F64, 159 | is_optional: false, 160 | default: Some(DataValue::F64(19.99,).into()), 161 | }, 162 | foreign: None, 163 | }, 164 | ColumnDef { 165 | column: ColumnName { 166 | name: "rating".to_string() 167 | }, 168 | attributes: None, 169 | data_type_def: DataTypeDef { 170 | data_type: DataType::Text, 171 | is_optional: true, 172 | default: Some( 173 | DataValue::Text("\'G\'::mpaa_rating".to_string(),) 174 | .into() 175 | ), 176 | }, 177 | foreign: None, 178 | }, 179 | ColumnDef { 180 | column: ColumnName { 181 | name: "last_update".to_string(), 182 | }, 183 | attributes: None, 184 | data_type_def: DataTypeDef { 185 | data_type: DataType::Utc, 186 | is_optional: false, 187 | default: None, 188 | }, 189 | foreign: None, 190 | }, 191 | ColumnDef { 192 | column: ColumnName { 193 | name: "special_features".to_string(), 194 | }, 195 | attributes: None, 196 | data_type_def: DataTypeDef { 197 | data_type: DataType::Text, 198 | is_optional: true, 199 | default: None, 200 | }, 201 | foreign: None, 202 | }, 203 | ColumnDef { 204 | column: ColumnName { 205 | name: "fulltext".to_string() 206 | }, 207 | attributes: None, 208 | data_type_def: DataTypeDef { 209 | data_type: DataType::Text, 210 | is_optional: false, 211 | default: None, 212 | }, 213 | foreign: None, 214 | }, 215 | ], 216 | } 217 | ) 218 | } 219 | 220 | #[test] 221 | fn table_def_with_default_function() { 222 | let input = r#"public.film{*film_id:uuid(uuid_generate_v4()),title:text,release_date:utc?(now())}"#; 223 | let input_chars = to_chars(input); 224 | let ret = table_def().parse(&input_chars).expect("must be parsed"); 225 | println!("ret: {:#?}", ret); 226 | assert_eq!( 227 | ret, 228 | TableDef { 229 | table: TableName { 230 | name: "public.film".to_string(), 231 | }, 232 | columns: vec![ 233 | ColumnDef { 234 | column: ColumnName { 235 | name: "film_id".to_string() 236 | }, 237 | attributes: Some(vec![ColumnAttribute::Primary,],), 238 | data_type_def: DataTypeDef { 239 | data_type: DataType::Uuid, 240 | is_optional: false, 241 | default: Some( 242 | Function { 243 | name: "uuid_generate_v4".into(), 244 | params: vec![], 245 | } 246 | .into() 247 | ), 248 | }, 249 | foreign: None, 250 | }, 251 | ColumnDef { 252 | column: ColumnName { 253 | name: "title".to_string() 254 | }, 255 | attributes: None, 256 | data_type_def: DataTypeDef { 257 | data_type: DataType::Text, 258 | is_optional: false, 259 | default: None, 260 | }, 261 | foreign: None, 262 | }, 263 | ColumnDef { 264 | column: ColumnName { 265 | name: "release_date".to_string(), 266 | }, 267 | attributes: None, 268 | data_type_def: DataTypeDef { 269 | data_type: DataType::Utc, 270 | is_optional: true, 271 | default: Some( 272 | Function { 273 | name: "now".to_string(), 274 | params: vec![] 275 | } 276 | .into() 277 | ), 278 | }, 279 | foreign: None, 280 | }, 281 | ], 282 | } 283 | ) 284 | } 285 | 286 | #[test] 287 | fn table_def_has_column_with_space() { 288 | let input = r#"public.film{*film_id:uuid(uuid_generate_v4()),"zip code":text,title:text,release_date:utc?(now())}"#; 289 | let input_chars = to_chars(input); 290 | let ret = table_def().parse(&input_chars).expect("must be parsed"); 291 | println!("ret: {:#?}", ret); 292 | assert_eq!( 293 | ret, 294 | TableDef { 295 | table: TableName { 296 | name: "public.film".to_string(), 297 | }, 298 | columns: vec![ 299 | ColumnDef { 300 | column: ColumnName { 301 | name: "film_id".to_string() 302 | }, 303 | attributes: Some(vec![ColumnAttribute::Primary,],), 304 | data_type_def: DataTypeDef { 305 | data_type: DataType::Uuid, 306 | is_optional: false, 307 | default: Some( 308 | Function { 309 | name: "uuid_generate_v4".into(), 310 | params: vec![], 311 | } 312 | .into() 313 | ), 314 | }, 315 | foreign: None, 316 | }, 317 | ColumnDef { 318 | column: ColumnName { 319 | name: "zip code".to_string() 320 | }, 321 | attributes: None, 322 | data_type_def: DataTypeDef { 323 | data_type: DataType::Text, 324 | is_optional: false, 325 | default: None, 326 | }, 327 | foreign: None, 328 | }, 329 | ColumnDef { 330 | column: ColumnName { 331 | name: "title".to_string() 332 | }, 333 | attributes: None, 334 | data_type_def: DataTypeDef { 335 | data_type: DataType::Text, 336 | is_optional: false, 337 | default: None, 338 | }, 339 | foreign: None, 340 | }, 341 | ColumnDef { 342 | column: ColumnName { 343 | name: "release_date".to_string(), 344 | }, 345 | attributes: None, 346 | data_type_def: DataTypeDef { 347 | data_type: DataType::Utc, 348 | is_optional: true, 349 | default: Some( 350 | Function { 351 | name: "now".to_string(), 352 | params: vec![] 353 | } 354 | .into() 355 | ), 356 | }, 357 | foreign: None, 358 | }, 359 | ], 360 | } 361 | ) 362 | } 363 | --------------------------------------------------------------------------------