Skip to content

Commit

Permalink
Merge pull request #53 from gwenn/extra_check
Browse files Browse the repository at this point in the history
Extra check
  • Loading branch information
gwenn authored May 5, 2024
2 parents 127915c + 4bb1464 commit 69c67e1
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 104 deletions.
59 changes: 56 additions & 3 deletions checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,45 +21,85 @@
## TODO

### `CREATE TABLE`
- [X] qualified (different of `temp`) temporary table

- [x] qualified (different of `temp`) temporary table

```sql
sqlite> ATTACH DATABASE ':memory:' AS mem;
sqlite> CREATE TEMPORARY TABLE mem.x AS SELECT 1;
Parse error: temporary table name must be unqualified
```

```sql
sqlite> CREATE TEMPORARY TABLE temp.x AS SELECT 1;
-- OK
```
- [X] must have at least one non-generated column

- [x] must have at least one non-generated column

```sql
sqlite> CREATE TABLE test(data AS (1));
Parse error: must have at least one non-generated column
```

- [ ] column constraint(s) checks

```sql
sqlite> CREATE TABLE t(a REFERENCES o(a,b));
Parse error: foreign key on a should reference only one column of table o
CREATE TABLE t(a REFERENCES o(a,b));
error here ---^
sqlite> CREATE TABLE t(a PRIMARY KEY AUTOINCREMENT) WITHOUT ROWID;
Parse error: AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY
sqlite> CREATE TABLE t(a INTEGER PRIMARY KEY AUTOINCREMENT) WITHOUT ROWID;
Parse error: AUTOINCREMENT not allowed on WITHOUT ROWID tables
```

- [ ] table constraint(s) checks

```sql
sqlite> CREATE TABLE test (a, b, FOREIGN KEY (b) REFERENCES test(a,b));
Parse error: number of columns in foreign key does not match the number of columns in the referenced table
```

```sql
sqlite> create table test (a,b, primary key(a), primary key(b));
Parse error: table "test" has more than one primary key
sqlite> create table test (a primary key, b primary key);
Parse error: table "test" has more than one primary key
sqlite> create table test (a primary key, b, primary key(a));
Parse error: table "test" has more than one primary key
```

### `HAVING`
- [X] HAVING clause on a non-aggregate query (`GroupBy::having`): grammar already prevents this case (grammar differs from SQLite official grammar).

- [x] HAVING clause on a non-aggregate query (`GroupBy::having`): grammar already prevents this case (grammar differs from SQLite official grammar).

```sql
sqlite> SELECT 1 as i HAVING i > 1;
Parse error: HAVING clause on a non-aggregate query
```

vs

```
[ERROR sqlite3Parser] near HAVING, "Token(None)": syntax error
Err: near HAVING, "None": syntax error at (1, 21) in SELECT 1 as i HAVING i > 1
```

### `SELECT ...`

- [ ] no duplicated column name in `selcollist`/`Select::columns`

```sql
sqlite> SELECT 1 as i, 2 as i;
-- no error (idem for postgres)
```

### `SELECT ... ORDER BY ...`

- [ ] ORDER BY term does not match any column in the result set (`Select::order_by`)

```sql
sqlite> SELECT 1 as i ORDER BY j;
Parse error: no such column: j
Expand All @@ -68,4 +108,17 @@ Parse error: no such column: j
```

### `WITH`

- [ ] no duplicated column name in `CommonTableExpr::IndexedColumn`

### DML

```sql
sqlite> CREATE TABLE test (n, m);
sqlite> INSERT INTO test (n, n, m) VALUES (1, 0, 1); -- pgsql KO
sqlite> SELECT * FROM test;
1|1
sqlite> UPDATE test SET n = 1, n = 0; -- pgsql KO
sqlite> SELECT * FROM test;
0|1
```
15 changes: 0 additions & 15 deletions src/lexer/README.md

This file was deleted.

20 changes: 18 additions & 2 deletions src/lexer/sql/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ fn duplicate_column() {
);
expect_parser_err_msg(
b"CREATE TABLE t (x TEXT, \"x\" TEXT)",
"duplicate column name: x",
"duplicate column name: \"x\"",
);
expect_parser_err_msg(
b"CREATE TABLE t (x TEXT, `x` TEXT)",
"duplicate column name: x",
"duplicate column name: `x`",
);
}

Expand Down Expand Up @@ -216,6 +216,14 @@ fn create_strict_table_unknown_datatype() {
);
}

#[test]
fn foreign_key_on_column() {
expect_parser_err_msg(
b"CREATE TABLE t(a REFERENCES o(a,b))",
"foreign key on a should reference only one column of table o",
);
}

#[test]
fn create_strict_table_generated_column() {
parse_cmd(
Expand Down Expand Up @@ -259,6 +267,14 @@ fn values_mismatch_columns_count() {
);
}

#[test]
fn column_specified_more_than_once() {
expect_parser_err_msg(
b"INSERT INTO t (n, n, m) VALUES (1, 0, 2)",
"column \"n\" specified more than once",
)
}

#[test]
fn alter_add_column_primary_key() {
expect_parser_err_msg(
Expand Down
2 changes: 1 addition & 1 deletion src/parser/ast/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ impl Stmt {
} => {
if *temporary {
if let Some(ref db_name) = tbl_name.db_name {
if Uncased::from_borrowed("TEMP") != db_name.0 {
if db_name != "TEMP" {
return Err(custom_err!("temporary table name must be unqualified"));
}
}
Expand Down
66 changes: 6 additions & 60 deletions src/parser/ast/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ impl ToTokens for Stmt {
tbl_name.to_tokens(s)?;
if let Some(columns) = columns {
s.append(TK_LP, None)?;
comma(columns, s)?;
comma(columns.deref(), s)?;
s.append(TK_RP, None)?;
}
body.to_tokens(s)?;
Expand Down Expand Up @@ -1101,7 +1101,7 @@ impl ToTokens for JoinConstraint {
JoinConstraint::Using(col_names) => {
s.append(TK_USING, None)?;
s.append(TK_LP, None)?;
comma(col_names, s)?;
comma(col_names.deref(), s)?;
s.append(TK_RP, None)
}
}
Expand Down Expand Up @@ -1588,10 +1588,10 @@ impl ToTokens for InsertBody {
impl ToTokens for Set {
fn to_tokens<S: TokenStream>(&self, s: &mut S) -> Result<(), S::Error> {
if self.col_names.len() == 1 {
comma(&self.col_names, s)?;
comma(self.col_names.deref(), s)?;
} else {
s.append(TK_LP, None)?;
comma(&self.col_names, s)?;
comma(self.col_names.deref(), s)?;
s.append(TK_RP, None)?;
}
s.append(TK_EQ, None)?;
Expand Down Expand Up @@ -1637,7 +1637,7 @@ impl ToTokens for TriggerEvent {
TriggerEvent::UpdateOf(ref col_names) => {
s.append(TK_UPDATE, None)?;
s.append(TK_OF, None)?;
comma(col_names, s)
comma(col_names.deref(), s)
}
}
}
Expand Down Expand Up @@ -1692,7 +1692,7 @@ impl ToTokens for TriggerCmd {
tbl_name.to_tokens(s)?;
if let Some(col_names) = col_names {
s.append(TK_LP, None)?;
comma(col_names, s)?;
comma(col_names.deref(), s)?;
s.append(TK_RP, None)?;
}
select.to_tokens(s)?;
Expand Down Expand Up @@ -2035,57 +2035,3 @@ fn double_quote<S: TokenStream>(name: &str, s: &mut S) -> Result<(), S::Error> {
f.write_char('"')*/
s.append(TK_ID, Some(name))
}

/// Convert an SQL-style quoted string into a normal string by removing
/// the quote characters.
pub fn dequote(n: Name) -> Result<Name, ParserError> {
let s = n.0.as_str();
if s.is_empty() {
return Ok(n);
}
let mut quote = s.chars().next().unwrap();
if quote != '"' && quote != '`' && quote != '\'' && quote != '[' {
return Ok(n);
} else if quote == '[' {
quote = ']';
}
debug_assert!(s.len() > 1);
debug_assert!(s.ends_with(quote));
let sub = &s[1..s.len() - 1];
let mut z = String::with_capacity(sub.len());
let mut escaped = false;
for c in sub.chars() {
if escaped {
if c != quote {
return Err(custom_err!("Malformed string literal: {}", s));
}
escaped = false;
} else if c == quote {
escaped = true;
continue;
}
z.push(c);
}
Ok(Name(Uncased::from_owned(z)))
}

#[cfg(test)]
mod test {
use super::{dequote, Name, ParserError};
use uncased::Uncased;

#[test]
fn test_dequote() -> Result<(), ParserError> {
assert_eq!(dequote(name("x"))?, name("x"));
assert_eq!(dequote(name("`x`"))?, name("x"));
assert_eq!(dequote(name("`x``y`"))?, name("x`y"));
assert_eq!(dequote(name(r#""x""#))?, name("x"));
assert_eq!(dequote(name(r#""x""y""#))?, name("x\"y"));
assert_eq!(dequote(name("[x]"))?, name("x"));
Ok(())
}

fn name(s: &'static str) -> Name {
Name(Uncased::from_borrowed(s))
}
}
Loading

0 comments on commit 69c67e1

Please sign in to comment.