From da153bf8486a908d4c291361b92ff9234d7b81af Mon Sep 17 00:00:00 2001 From: Jovansonlee Cesar Date: Fri, 28 Sep 2018 03:32:10 +0800 Subject: [PATCH] Make a PostgreSQLDialect Add is_primary and is_unique in the column definition Initial code for testing alter table --- src/dialect.rs | 105 +++++++++++++++++++++++++++++++++++++++----- src/sqlast.rs | 2 + src/sqlparser.rs | 43 +++++++++++++++--- src/sqltokenizer.rs | 8 ---- 4 files changed, 135 insertions(+), 23 deletions(-) diff --git a/src/dialect.rs b/src/dialect.rs index e46ba5f6..a170256b 100644 --- a/src/dialect.rs +++ b/src/dialect.rs @@ -433,16 +433,101 @@ impl Dialect for GenericSqlDialect { "DATE", "TIME", "TIMESTAMP", - "VALUES", - "DEFAULT", - "ZONE", - "REGCLASS", - "TEXT", - "BYTEA", - "TRUE", - "FALSE", - "COPY", - "STDIN", + ]; + } + + fn is_identifier_start(&self, ch: char) -> bool { + (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '@' + } + + fn is_identifier_part(&self, ch: char) -> bool { + (ch >= 'a' && ch <= 'z') + || (ch >= 'A' && ch <= 'Z') + || (ch >= '0' && ch <= '9') + || ch == '@' + || ch == '_' + } +} + +pub struct PostgreSqlDialect {} + +impl Dialect for PostgreSqlDialect { + fn keywords(&self) -> Vec<&'static str> { + return vec![ + "SELECT", + "FROM", + "WHERE", + "LIMIT", + "ORDER", + "GROUP", + "BY", + "HAVING", + "UNION", + "ALL", + "INSERT", + "INTO", + "UPDATE", + "DELETE", + "IN", + "IS", + "NULL", + "SET", + "CREATE", + "EXTERNAL", + "TABLE", + "ASC", + "DESC", + "AND", + "OR", + "NOT", + "AS", + "STORED", + "CSV", + "PARQUET", + "LOCATION", + "WITH", + "WITHOUT", + "HEADER", + "ROW", + // SQL types + "CHAR", + "CHARACTER", + "VARYING", + "LARGE", + "OBJECT", + "VARCHAR", + "CLOB", + "BINARY", + "VARBINARY", + "BLOB", + "FLOAT", + "REAL", + "DOUBLE", + "PRECISION", + "INT", + "INTEGER", + "SMALLINT", + "BIGINT", + "NUMERIC", + "DECIMAL", + "DEC", + "BOOLEAN", + "DATE", + "TIME", + "TIMESTAMP", + "VALUES", + "DEFAULT", + "ZONE", + "REGCLASS", + "TEXT", + "BYTEA", + "TRUE", + "FALSE", + "COPY", + "STDIN", + "PRIMARY", + "KEY", + "UNIQUE", ]; } diff --git a/src/sqlast.rs b/src/sqlast.rs index d40ca76c..e2ebd610 100644 --- a/src/sqlast.rs +++ b/src/sqlast.rs @@ -176,6 +176,8 @@ pub struct SQLColumnDef { pub name: String, pub data_type: SQLType, pub allow_null: bool, + pub is_primary: bool, + pub is_unique: bool, pub default: Option>, } diff --git a/src/sqlparser.rs b/src/sqlparser.rs index 8f308f1d..7e89564e 100644 --- a/src/sqlparser.rs +++ b/src/sqlparser.rs @@ -478,6 +478,8 @@ impl Parser { loop { if let Some(Token::Identifier(column_name)) = self.next_token() { if let Ok(data_type) = self.parse_data_type() { + let is_primary = self.parse_keywords(vec!["PRIMARY", "KEY"]); + let is_unique = self.parse_keyword("UNIQUE"); let default = if self.parse_keyword("DEFAULT"){ let expr = self.parse_expr(0)?; Some(Box::new(expr)) @@ -500,6 +502,8 @@ impl Parser { name: column_name, data_type: data_type, allow_null, + is_primary, + is_unique, default, }); } @@ -509,6 +513,8 @@ impl Parser { name: column_name, data_type: data_type, allow_null, + is_primary, + is_unique, default, }); break; @@ -538,6 +544,7 @@ impl Parser { } } + /// Parse a copy statement pub fn parse_copy(&mut self) -> Result { let table_name = self.parse_tablename()?; @@ -1090,13 +1097,13 @@ impl Parser { #[cfg(test)] mod tests { - use super::super::dialect::GenericSqlDialect; + use super::super::dialect::PostgreSqlDialect; use super::*; #[test] fn test_prev_index(){ let sql: &str = "SELECT version()"; - let mut tokenizer = Tokenizer::new(&GenericSqlDialect{}, &sql); + let mut tokenizer = Tokenizer::new(&PostgreSqlDialect{}, &sql); let tokens = tokenizer.tokenize().expect("error tokenizing"); let mut parser = Parser::new(tokens); assert_eq!(parser.prev_token(), None); @@ -1542,9 +1549,9 @@ mod tests { fn parse_create_table_with_inherit() { let sql = String::from(" CREATE TABLE bazaar.settings ( - user_id uuid, + settings_id uuid PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL, + user_id uuid UNIQUE, value text[], - settings_id uuid DEFAULT uuid_generate_v4() NOT NULL, use_metric boolean DEFAULT true ) INHERITS (system.record)"); @@ -1554,14 +1561,40 @@ mod tests { assert_eq!("bazaar.settings", name); let c_name = &columns[0]; + assert_eq!("settings_id", c_name.name); + assert_eq!(SQLType::Custom("uuid".into()), c_name.data_type); + assert_eq!(false, c_name.allow_null); + assert_eq!(true, c_name.is_primary); + assert_eq!(false, c_name.is_unique); + + let c_name = &columns[1]; assert_eq!("user_id", c_name.name); assert_eq!(SQLType::Custom("uuid".into()), c_name.data_type); assert_eq!(true, c_name.allow_null); + assert_eq!(false, c_name.is_primary); + assert_eq!(true, c_name.is_unique); } _ => assert!(false), } } + #[test] + fn parse_alter_table_constraint_primary_key(){ + let sql = String::from(" + ALTER TABLE ONLY bazaar.address + ADD CONSTRAINT address_pkey PRIMARY KEY (address_id)"); + let ast = parse_sql(&sql); + } + + #[test] + fn parse_alter_table_constraint_foreign_key(){ + let sql = String::from(" + ALTER TABLE ONLY public.customer + ADD CONSTRAINT customer_address_id_fkey FOREIGN KEY (address_id) REFERENCES public.address(address_id) ON UPDATE CASCADE ON DELETE RESTRICT; + "); + let ast = parse_sql(&sql); + } + #[test] fn parse_copy_example(){ let sql = String::from(r#" @@ -1681,7 +1714,7 @@ PHP ₱ USD $ } fn parser(sql: &str) -> Parser { - let dialect = GenericSqlDialect {}; + let dialect = PostgreSqlDialect {}; let mut tokenizer = Tokenizer::new(&dialect, &sql); let tokens = tokenizer.tokenize().unwrap(); debug!("tokens: {:#?}", tokens); diff --git a/src/sqltokenizer.rs b/src/sqltokenizer.rs index f9104d95..6abbebc1 100644 --- a/src/sqltokenizer.rs +++ b/src/sqltokenizer.rs @@ -203,14 +203,6 @@ impl<'a> Tokenizer<'a> { tokens.push(token); } Ok(tokens) - /* - Ok(tokens - .into_iter() - .filter(|t| match t { - Token::Whitespace(..) => false, - _ => true, - }).collect()) - */ } /// Get the next token or return None