///|
fn Parser::parse_create_table_statement(
self : Parser,
tokens : ArrayView[Token],
) -> ParserResult[CreateTableStmt] raise ParserError {
let tokens = self.expect_token(tokens, Keyword(Create))
let tokens = self.expect_token(tokens, Keyword(Table))
// Parse optional IF NOT EXISTS
let (if_not_exists, tokens) = match tokens {
[Keyword(If), Keyword(Not), Keyword(Exists), .. tokens] => (true, tokens)
_ => (false, tokens)
}
let (name, tokens) = match tokens {
[Token::Identifier(name), .. tokens] => (name, tokens)
[Token::StringLiteral(name), .. tokens] => (name, tokens)
_ =>
raise UnexpectedTokenError(
tokens[0],
Token::Identifier("table_name".to_string()),
)
}
// Parse table definition - either columns or AS SELECT
let (definition, tokens) = match tokens {
[Keyword(As), .. tokens] => {
let (query, tokens) = self.parse_query(tokens)
(CreateTableDefinition::AsQuery(query), tokens)
}
_ => {
let ((columns, constraints), tokens) = self.parse_column_defs(tokens)
(CreateTableDefinition::Columns(columns, constraints), tokens)
}
}
({ name, if_not_exists, definition }, tokens)
}
///| Parse CREATE INDEX statement
fn Parser::parse_create_index_statement(
self : Parser,
tokens : ArrayView[Token],
) -> ParserResult[CreateIndexStmt] raise ParserError {
let tokens = self.expect_token(tokens, Keyword(Create))
// Parse optional UNIQUE
let (unique, tokens) = match tokens {
[Keyword(Unique), .. tokens] => (true, tokens)
_ => (false, tokens)
}
let tokens = self.expect_token(tokens, Keyword(Index))
// Parse optional CONCURRENTLY
let (concurrently, tokens) = match tokens {
[Keyword(Concurrently), .. tokens] => (true, tokens)
_ => (false, tokens)
}
// Parse optional IF NOT EXISTS
let (if_not_exists, tokens) = match tokens {
[Keyword(If), Keyword(Not), Keyword(Exists), .. tokens] => (true, tokens)
_ => (false, tokens)
}
// Parse index name
let (name, tokens) = match tokens {
[Token::Identifier(name), .. tokens] => (name, tokens)
[Token::StringLiteral(name), .. tokens] => (name, tokens)
[token, .. _] => raise UnexpectedTokenError(token, Token::Identifier("index_name".to_string()))
[] => raise ParserError::InternalBug("parse_create_index_statement: unexpected end of tokens")
}
// Parse ON table_name
let tokens = self.expect_token(tokens, Keyword(On))
let (table_name, tokens) = self.parse_object_name(tokens)
// Parse optional USING method
let (index_method, tokens) = match tokens {
[Keyword(Using), Keyword(Btree), .. tokens] => (Some(IndexMethod::Btree), tokens)
[Keyword(Using), Keyword(Hash), .. tokens] => (Some(IndexMethod::Hash), tokens)
[Keyword(Using), Keyword(Gin), .. tokens] => (Some(IndexMethod::Gin), tokens)
[Keyword(Using), Keyword(Gist), .. tokens] => (Some(IndexMethod::Gist), tokens)
[Keyword(Using), Keyword(Spgist), .. tokens] => (Some(IndexMethod::Spgist), tokens)
[Keyword(Using), Keyword(Brin), .. tokens] => (Some(IndexMethod::Brin), tokens)
_ => (None, tokens)
}
// Parse column list
let tokens = self.expect_token(tokens, LParen)
let columns = []
let tokens = loop tokens {
[RParen, .. rest_tokens] => break rest_tokens
rest_tokens => {
let (column_expr, rest_tokens) = self.parse_expr(rest_tokens)
// Parse optional ASC/DESC
let (asc, rest_tokens) = match rest_tokens {
[Keyword(Asc), .. rest_tokens] => (Some(true), rest_tokens)
[Keyword(Desc), .. rest_tokens] => (Some(false), rest_tokens)
_ => (None, rest_tokens)
}
// Parse optional NULLS FIRST/LAST
let (nulls_first, rest_tokens) = match rest_tokens {
[Keyword(Nulls), Keyword(First), .. rest_tokens] => (Some(true), rest_tokens)
[Keyword(Nulls), Keyword(Last), .. rest_tokens] => (Some(false), rest_tokens)
_ => (None, rest_tokens)
}
columns.push({ name: column_expr, asc, nulls_first })
match rest_tokens {
[Comma, .. rest_tokens] => continue rest_tokens
[RParen, .. rest_tokens] => break rest_tokens
[token, .. _] => raise UnexpectedTokenError(token, RParen)
[] => raise ParserError::InternalBug("parse_create_index_statement: unexpected end of tokens")
}
}
}
// Parse optional WHERE clause
let (where_clause, tokens) = match tokens {
[Keyword(Where), .. tokens] => {
let (expr, tokens) = self.parse_expr(tokens)
(Some(expr), tokens)
}
_ => (None, tokens)
}
({
unique,
concurrently,
if_not_exists,
name,
table_name,
index_method,
columns,
where_clause,
}, tokens)
}
///| CREATE INDEX Tests
test "CREATE INDEX - Simple" {
let tokens = "CREATE INDEX idx_name ON users (email);"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE INDEX idx_name ON users (email);
),
)
}
test "CREATE UNIQUE INDEX" {
let tokens = "CREATE UNIQUE INDEX idx_unique_email ON users (email);"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE UNIQUE INDEX idx_unique_email ON users (email);
),
)
}
test "CREATE INDEX with multiple columns" {
let tokens = "CREATE INDEX idx_name_age ON users (name, age DESC);"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE INDEX idx_name_age ON users (name, age DESC);
),
)
}
test "CREATE INDEX with USING method" {
let tokens = "CREATE INDEX idx_hash ON users USING HASH (id);"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE INDEX idx_hash ON users USING HASH (id);
),
)
}
test "CREATE INDEX with WHERE clause" {
let tokens = "CREATE INDEX idx_active_users ON users (created_at) WHERE active = true;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE INDEX idx_active_users ON users (created_at) WHERE active = TRUE;
),
)
}
test "CREATE INDEX CONCURRENTLY" {
let tokens = "CREATE INDEX CONCURRENTLY idx_concurrent ON users (email);"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE INDEX CONCURRENTLY idx_concurrent ON users (email);
),
)
}
test "CREATE INDEX IF NOT EXISTS" {
let tokens = "CREATE INDEX IF NOT EXISTS idx_exists ON users (email);"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE INDEX IF NOT EXISTS idx_exists ON users (email);
),
)
}
test "CREATE INDEX with NULLS ordering" {
let tokens = "CREATE INDEX idx_ordered ON users (name ASC NULLS FIRST, age DESC NULLS LAST);"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE INDEX idx_ordered ON users (name ASC NULLS FIRST, age DESC NULLS LAST);
),
)
}
test "CREATE INDEX with expression" {
let tokens = "CREATE INDEX idx_lower_email ON users (LOWER(email));"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE INDEX idx_lower_email ON users (lower(email));
),
)
}
test "CREATE UNIQUE INDEX with all options" {
let tokens = "CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS idx_complex ON users USING BTREE (email ASC NULLS FIRST, created_at DESC) WHERE active = true;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS idx_complex ON users USING BTREE (email ASC NULLS FIRST, created_at DESC) WHERE active = TRUE;
),
)
}
///|
test "Create table" {
let tokens = "CREATE TABLE users;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE TABLE users (
#|
#|);
),
)
}
///|
test "Create table IF NOT EXISTS" {
let tokens = "CREATE TABLE IF NOT EXISTS users (id INTEGER);"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE TABLE IF NOT EXISTS users (
#| id INTEGER
#|);
),
)
}
///|
test "Create table AS SELECT" {
let tokens = "CREATE TABLE students_2 AS SELECT * FROM students;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE TABLE students_2 AS SELECT
#| *
#|FROM
#| students;
),
)
}
///|
test "Create table AS SELECT with WHERE" {
let tokens = "CREATE TABLE students_3 AS SELECT city, grade FROM students WHERE grade > 3.0;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE TABLE students_3 AS SELECT
#| city,
#| grade
#|FROM
#| students
#|WHERE
#| grade > 3;
),
)
}
///|
fn Parser::parse_column_defs(
self : Parser,
tokens : ArrayView[Token],
) -> ParserResult[(Array[ColumnDef], Array[TableConstraint])] raise ParserError {
let columns = []
let constraints = []
let tokens = if tokens is [Token::LParen, .. tokens] {
loop tokens {
[Identifier(name), .. tokens] => {
let (data_type, tokens) = self.parse_data_type(tokens)
let (options, tokens) = self.parse_column_def_option(tokens)
columns.push({ name, data_type, options })
match tokens {
[Comma, .. tokens] => continue tokens
[RParen, .. tokens] => break tokens
[token, ..] =>
raise UnexpectedTokenMessageError(
token, "expected ',' or ')', found",
)
[] => raise InternalBug("parse_column_defs: unexpected end of tokens")
}
continue tokens
}
tokens => {
let (constraint, tokens) = self.parse_table_constraint(tokens)
constraints.push(constraint)
match tokens {
[Comma, .. tokens] => continue tokens
[RParen, .. tokens] => break tokens
[token, .. _tokens] =>
raise UnexpectedTokenMessageError(
token, "expected ',' or ')', found",
)
[] => raise InternalBug("parse_column_defs: unexpected end of tokens")
}
}
}
} else {
tokens
}
((columns, constraints), tokens)
}
///|
fn Parser::parse_data_type(
_self : Parser,
tokens : ArrayView[Token],
) -> ParserResult[DataType] raise ParserError {
match tokens {
[Keyword(Integer) | Keyword(Int), .. tokens] => (Integer, tokens)
[Keyword(Smallint), .. tokens] => (Smallint, tokens)
[Keyword(Bigint), .. tokens] => (Bigint, tokens)
[Keyword(Float), LParen, Number(n), RParen, .. tokens] => {
let n = @strconv.parse_int(n) catch {
_ => raise ParserError::InvalidDataTypeError(n)
}
(Float(Some(n)), tokens)
}
[Keyword(Float), .. tokens] => (Float(None), tokens)
[Keyword(Real), .. tokens] => (Real, tokens)
[Keyword(Double), .. tokens] => (Double, tokens)
[Keyword(Char), LParen, Number(n), RParen, .. tokens] => {
let n = @strconv.parse_int(n) catch {
_ => raise ParserError::InvalidDataTypeError(n)
}
(Char(n), tokens)
}
[Keyword(Varchar), LParen, Number(n), RParen, .. tokens] => {
let n = @strconv.parse_int(n) catch {
_ => raise ParserError::InvalidDataTypeError(n)
}
(Varchar(n), tokens)
}
[Keyword(Text), .. tokens] => (Text, tokens)
[Keyword(Boolean), .. tokens] => (Boolean, tokens)
[Keyword(Timestamp), .. tokens] => (Timestamp, tokens)
[Keyword(Blob), .. tokens] => (Blob, tokens)
[token, .. _tokens] =>
raise UnexpectedTokenMessageError(token, "expected data type")
[] => raise InternalBug("parse_data_type: unexpected end of tokens")
}
}
///|
test "Create table with two columns" {
let tokens = "CREATE TABLE users (id INTEGER, name INTEGER);"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE TABLE users (
#| id INTEGER,
#| name INTEGER
#|);
),
)
}
///|
test "Many kinds of data types" {
let tokens =
#|CREATE TABLE users (
#| id INTEGER,
#| name VARCHAR(100),
#| age INT,
#| active BOOLEAN,
#| balance FLOAT(2),
#| created_at TIMESTAMP,
#| updated_at TIMESTAMP,
#| description TEXT,
#| profile_picture BLOB
#|);
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE TABLE users (
#| id INTEGER,
#| name VARCHAR(100),
#| age INTEGER,
#| active BOOLEAN,
#| balance FLOAT(2),
#| created_at TIMESTAMP,
#| updated_at TIMESTAMP,
#| description TEXT,
#| profile_picture BLOB
#|);
),
)
}
///|
fn Parser::parse_column_def_option(
self : Parser,
tokens : ArrayView[Token],
) -> ParserResult[Array[ColumnDefOption]] raise ParserError {
let options = []
let tokens = loop tokens {
[Keyword(Primary), Keyword(Key), .. tokens] => {
options.push(ColumnDefOption::PrimaryKey)
continue tokens
}
[Keyword(Unique), .. tokens] => {
options.push(ColumnDefOption::Unique)
continue tokens
}
[Keyword(Not), Keyword(Null), .. tokens] => {
options.push(NotNull)
continue tokens
}
[Keyword(Default), .. tokens] => {
let (expr, tokens) = self.parse_expr(tokens)
options.push(Default(expr))
continue tokens
}
tokens => break tokens
}
(options, tokens)
}
///|
test "With options" {
let tokens = "CREATE TABLE users (id INTEGER NOT NULL UNIQUE, name INTEGER DEFAULT 18);"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE TABLE users (
#| id INTEGER NOT NULL UNIQUE,
#| name INTEGER DEFAULT 18
#|);
),
)
}
///|
fn Parser::parse_create_view_statement(
self : Parser,
tokens : ArrayView[Token],
) -> ParserResult[CreateViewStmt] raise ParserError {
let tokens = self.expect_token(tokens, Keyword(Create))
let tokens = self.expect_token(tokens, Keyword(View))
guard tokens is [Identifier(name), .. tokens] else {
raise UnexpectedTokenError(
tokens[0],
Token::Identifier("view_name".to_string()),
)
}
let columns = []
let tokens = if tokens is [LParen, .. tokens] {
loop tokens {
tokens => {
let (column, tokens) = self.parse_view_column_def(tokens)
columns.push(column)
match tokens {
[Comma, .. tokens] => continue tokens
[RParen, .. tokens] => break tokens
[token, .. _tokens] =>
raise UnexpectedTokenMessageError(
token, "expected ',' or ')', found",
)
[] =>
raise InternalBug(
"parse_create_view_statement: unexpected end of tokens",
)
}
}
}
} else {
tokens
}
let tokens = self.expect_token(tokens, Keyword(As))
let (query, tokens) = self.parse_query(tokens)
({ name, columns, query }, tokens)
}
///|
fn Parser::parse_view_column_def(
_self : Parser,
tokens : ArrayView[Token],
) -> ParserResult[ViewColumnDef] raise ParserError {
match tokens {
[Identifier(name), .. tokens] => ({ name, }, tokens)
[token, .. _tokens] =>
raise UnexpectedTokenMessageError(token, "expected column definition")
[] => raise InternalBug("parse_view_column_def: unexpected end of tokens")
}
}
///|
test "Create view with columns" {
let tokens = "CREATE VIEW user_view (id, name) AS SELECT id, name FROM users;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE VIEW user_view (id, name) AS
#|SELECT
#| id,
#| name
#|FROM
#| users;
),
)
}
///|
fn Parser::parse_drop_view_statement(
self : Parser,
tokens : ArrayView[Token],
) -> ParserResult[DropViewStmt] raise ParserError {
let tokens = self.expect_token(tokens, Keyword(Drop))
let tokens = self.expect_token(tokens, Keyword(View))
guard tokens is [Identifier(name), .. tokens] else {
raise UnexpectedTokenError(
tokens[0],
Token::Identifier("view_name".to_string()),
)
}
(DropViewStmt::{ name, }, tokens)
}
///|
test "Drop view statement" {
let tokens = "DROP VIEW user_view;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|DROP VIEW user_view;
),
)
}
///|
fn Parser::parse_table_constraint(
self : Parser,
tokens : ArrayView[Token],
) -> ParserResult[TableConstraint] raise ParserError {
match tokens {
[Keyword(Primary), Keyword(Key), LParen, .. tokens] => {
let (columns, tokens) = self.parse_order_by(tokens)
let tokens = self.expect_token(tokens, RParen)
(TableConstraint::PrimaryKey(columns), tokens)
}
[Keyword(Unique), LParen, .. tokens] => {
let (columns, tokens) = self.parse_order_by(tokens)
let tokens = self.expect_token(tokens, RParen)
(TableConstraint::Unique(columns), tokens)
}
[Keyword(Foreign), Keyword(Key), LParen, ..] as tokens =>
self.parse_foreign_key_constraint(tokens)
[Keyword(Check), LParen, .. tokens] => {
let (expr, tokens) = self.parse_expr(tokens)
let tokens = self.expect_token(tokens, RParen)
(TableConstraint::Check(expr), tokens)
}
[token, .. _tokens] =>
raise UnexpectedTokenMessageError(token, "expected table constraint")
[] => raise InternalBug("parse_table_constraint: unexpected end of tokens")
}
}
///|
test "Table constraints" {
let tokens =
#|CREATE TABLE users (
#| id INTEGER PRIMARY KEY,
#| name VARCHAR(100) UNIQUE,
#| age INTEGER,
#| PRIMARY KEY (id, name),
#| UNIQUE (name)
#|);
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE TABLE users (
#| id INTEGER PRIMARY KEY,
#| name VARCHAR(100) UNIQUE,
#| age INTEGER,
#| PRIMARY KEY (id, name),
#| UNIQUE (name)
#|);
),
)
}
///|
fn Parser::parse_foreign_key_constraint(
self : Parser,
tokens : ArrayView[Token],
) -> ParserResult[TableConstraint] raise ParserError {
let tokens = self.expect_token(tokens, Keyword(Foreign))
let tokens = self.expect_token(tokens, Keyword(Key))
guard tokens is [LParen, .. tokens] else {
raise UnexpectedTokenError(tokens[0], Token::LParen)
}
let (columns, tokens) = self.parse_order_by(tokens)
let tokens = self.expect_token(tokens, RParen)
let tokens = self.expect_token(tokens, Keyword(References))
let (foreign_table, tokens) = self.parse_object_name(tokens)
let foreign_columns = []
let tokens = if tokens is [LParen, .. tokens] {
loop tokens {
[Identifier(col), Comma, .. tokens] => {
foreign_columns.push(col)
continue tokens
}
[Identifier(col), RParen, .. tokens] => {
foreign_columns.push(col)
break tokens
}
[token, .. _tokens] =>
raise UnexpectedTokenMessageError(
token, "expected foreign key column definition",
)
[] =>
raise InternalBug(
"parse_foreign_key_constraint: unexpected end of tokens",
)
}
} else {
tokens
}
(ForeignKey(foreign_columns~, foreign_table~, columns~), tokens)
}
///|
test "Foreign key" {
let tokens =
#|CREATE TABLE users (
#| id INTEGER PRIMARY KEY,
#| name VARCHAR(100) UNIQUE,
#| FOREIGN KEY (id) REFERENCES other_table (other_id)
#|);
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE TABLE users (
#| id INTEGER PRIMARY KEY,
#| name VARCHAR(100) UNIQUE,
#| FOREIGN KEY (id) REFERENCES other_table (other_id)
#|);
),
)
}
///|
test "Foreign key without columns" {
let tokens =
#|CREATE TABLE users (
#| id INTEGER PRIMARY KEY,
#| name VARCHAR(100) UNIQUE,
#| FOREIGN KEY (name) REFERENCES other_table
#|);
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE TABLE users (
#| id INTEGER PRIMARY KEY,
#| name VARCHAR(100) UNIQUE,
#| FOREIGN KEY (name) REFERENCES other_table
#|);
),
)
}
///|
test "Check" {
let tokens =
#|CREATE TABLE users (
#| id INTEGER PRIMARY KEY,
#| name VARCHAR(100) UNIQUE,
#| CHECK (id > 0)
#|);
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE TABLE users (
#| id INTEGER PRIMARY KEY,
#| name VARCHAR(100) UNIQUE,
#| CHECK (id > 0)
#|);
),
)
}
///|
fn Parser::parse_drop_table_statement(
self : Parser,
tokens : ArrayView[Token],
) -> ParserResult[DropTableStmt] raise ParserError {
let tokens = self.expect_token(tokens, Keyword(Drop))
let tokens = self.expect_token(tokens, Keyword(Table))
// Parse optional IF EXISTS
let (if_exists, tokens) = match tokens {
[Keyword(If), Keyword(Exists), .. tokens] => (true, tokens)
_ => (false, tokens)
}
let (table_name, tokens) = self.parse_object_name(tokens)
({ table_name, if_exists, }, tokens)
}
///|
test "Drop table" {
let tokens = "DROP TABLE students;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|DROP TABLE students;
),
)
}
///|
test "Drop table IF EXISTS" {
let tokens = "DROP TABLE IF EXISTS students;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|DROP TABLE IF EXISTS students;
),
)
}
///|
fn Parser::parse_drop_index_statement(
self : Parser,
tokens : ArrayView[Token],
) -> ParserResult[DropIndexStmt] raise ParserError {
let tokens = self.expect_token(tokens, Keyword(Drop))
let tokens = self.expect_token(tokens, Keyword(Index))
// Parse optional CONCURRENTLY (PostgreSQL)
let (concurrently, tokens) = match tokens {
[Keyword(Concurrently), .. tokens] => (true, tokens)
_ => (false, tokens)
}
// Parse optional IF EXISTS
let (if_exists, tokens) = match tokens {
[Keyword(If), Keyword(Exists), .. tokens] => (true, tokens)
_ => (false, tokens)
}
// Parse index name
let (name, tokens) = match tokens {
[Token::Identifier(name), .. tokens] => (name, tokens)
[Token::StringLiteral(name), .. tokens] => (name, tokens)
[token, .. _] => raise UnexpectedTokenError(token, Token::Identifier("index_name".to_string()))
[] => raise ParserError::InternalBug("parse_drop_index_statement: unexpected end of tokens")
}
// Parse optional ON table_name (some dialects)
let (table_name, tokens) = match tokens {
[Keyword(On), .. tokens] => {
let (table_name, tokens) = self.parse_object_name(tokens)
(Some(table_name), tokens)
}
_ => (None, tokens)
}
({ name, if_exists, concurrently, table_name }, tokens)
}
///|
test "DROP INDEX - Simple" {
let tokens = "DROP INDEX idx_name;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|DROP INDEX idx_name;
),
)
}
///|
test "DROP INDEX IF EXISTS" {
let tokens = "DROP INDEX IF EXISTS idx_name;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|DROP INDEX IF EXISTS idx_name;
),
)
}
///|
test "DROP INDEX CONCURRENTLY" {
let tokens = "DROP INDEX CONCURRENTLY idx_name;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|DROP INDEX CONCURRENTLY idx_name;
),
)
}
///|
test "DROP INDEX CONCURRENTLY IF EXISTS" {
let tokens = "DROP INDEX CONCURRENTLY IF EXISTS idx_name;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|DROP INDEX CONCURRENTLY IF EXISTS idx_name;
),
)
}
///|
test "DROP INDEX with table name" {
let tokens = "DROP INDEX idx_name ON users;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|DROP INDEX idx_name ON users;
),
)
}
///|
fn Parser::parse_alter_table_statement(
self : Parser,
tokens : ArrayView[Token],
) -> ParserResult[AlterTableStmt] raise ParserError {
let tokens = self.expect_token(tokens, Keyword(Alter))
let tokens = self.expect_token(tokens, Keyword(Table))
// Parse optional IF EXISTS
let (if_exists, tokens) = match tokens {
[Keyword(If), Keyword(Exists), .. tokens] => (true, tokens)
_ => (false, tokens)
}
let (table_name, tokens) = self.parse_object_name(tokens)
// Parse table operation
let (operation, tokens) = match tokens {
[Keyword(Drop), Keyword(Column), .. tokens] => {
// Parse optional IF EXISTS for column
let (column_if_exists, tokens) = match tokens {
[Keyword(If), Keyword(Exists), .. tokens] => (true, tokens)
_ => (false, tokens)
}
match tokens {
[Identifier(column_name), .. tokens] =>
(AlterTableOperation::DropColumn(column_name, column_if_exists), tokens)
[token, .. _tokens] =>
raise UnexpectedTokenMessageError(token, "expected column name")
[] => raise InternalBug("parse_alter_table_statement: unexpected end of tokens")
}
}
[token, .. _tokens] =>
raise UnexpectedTokenMessageError(token, "expected table operation")
[] => raise InternalBug("parse_alter_table_statement: unexpected end of tokens")
}
({ table_name, if_exists, operation, }, tokens)
}
///|
test "Alter table drop column" {
let tokens = "ALTER TABLE mytable DROP COLUMN mycolumn;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|ALTER TABLE mytable DROP COLUMN mycolumn;
),
)
}
///|
test "Alter table drop column IF EXISTS" {
let tokens = "ALTER TABLE mytable DROP COLUMN IF EXISTS mycolumn;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|ALTER TABLE mytable DROP COLUMN IF EXISTS mycolumn;
),
)
}
///|
test "Alter table IF EXISTS drop column IF EXISTS" {
let tokens = "ALTER TABLE IF EXISTS mytable DROP COLUMN IF EXISTS mycolumn;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|ALTER TABLE IF EXISTS mytable DROP COLUMN IF EXISTS mycolumn;
),
)
}
///|
fn Parser::parse_create_database_statement(
self : Parser,
tokens : ArrayView[Token],
) -> ParserResult[CreateDatabaseStmt] raise ParserError {
let tokens = self.expect_token(tokens, Keyword(Create))
let tokens = self.expect_token(tokens, Keyword(Database))
// Parse optional IF NOT EXISTS
let (if_not_exists, tokens) = match tokens {
[Keyword(If), Keyword(Not), Keyword(Exists), .. tokens] => (true, tokens)
_ => (false, tokens)
}
// Parse database name
let (name, tokens) = match tokens {
[Token::Identifier(name), .. tokens] => (name, tokens)
[Token::StringLiteral(name), .. tokens] => (name, tokens)
[token, .. _] => raise UnexpectedTokenError(token, Token::Identifier("database_name".to_string()))
[] => raise ParserError::InternalBug("parse_create_database_statement: unexpected end of tokens")
}
// Parse optional CHARACTER SET
let (character_set, tokens) = match tokens {
[Keyword(Character), Keyword(Set), Token::Identifier(charset), .. tokens] =>
(Some(charset), tokens)
[Keyword(Character), Keyword(Set), Token::StringLiteral(charset), .. tokens] =>
(Some(charset), tokens)
_ => (None, tokens)
}
// Parse optional COLLATE
let (collate, tokens) = match tokens {
[Keyword(Collate), Token::Identifier(collation), .. tokens] =>
(Some(collation), tokens)
[Keyword(Collate), Token::StringLiteral(collation), .. tokens] =>
(Some(collation), tokens)
_ => (None, tokens)
}
({ name, if_not_exists, character_set, collate }, tokens)
}
///|
fn Parser::parse_create_schema_statement(
self : Parser,
tokens : ArrayView[Token],
) -> ParserResult[CreateSchemaStmt] raise ParserError {
let tokens = self.expect_token(tokens, Keyword(Create))
let tokens = self.expect_token(tokens, Keyword(Schema))
// Parse optional IF NOT EXISTS
let (if_not_exists, tokens) = match tokens {
[Keyword(If), Keyword(Not), Keyword(Exists), .. tokens] => (true, tokens)
_ => (false, tokens)
}
// Parse schema name
let (name, tokens) = match tokens {
[Token::Identifier(name), .. tokens] => (name, tokens)
[Token::StringLiteral(name), .. tokens] => (name, tokens)
[token, .. _] => raise UnexpectedTokenError(token, Token::Identifier("schema_name".to_string()))
[] => raise ParserError::InternalBug("parse_create_schema_statement: unexpected end of tokens")
}
// Parse optional AUTHORIZATION
let (authorization, tokens) = match tokens {
[Keyword(Authorization), Token::Identifier(user), .. tokens] =>
(Some(user), tokens)
[Keyword(Authorization), Token::StringLiteral(user), .. tokens] =>
(Some(user), tokens)
_ => (None, tokens)
}
({ name, if_not_exists, authorization }, tokens)
}
///|
test "CREATE DATABASE - Simple" {
let tokens = "CREATE DATABASE mydb;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE DATABASE mydb;
),
)
}
///|
test "CREATE DATABASE IF NOT EXISTS" {
let tokens = "CREATE DATABASE IF NOT EXISTS mydb;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE DATABASE IF NOT EXISTS mydb;
),
)
}
///|
test "CREATE DATABASE with CHARACTER SET" {
let tokens = "CREATE DATABASE mydb CHARACTER SET utf8;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE DATABASE mydb CHARACTER SET utf8;
),
)
}
///|
test "CREATE DATABASE with CHARACTER SET and COLLATE" {
let tokens = "CREATE DATABASE mydb CHARACTER SET utf8 COLLATE utf8_general_ci;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE DATABASE mydb CHARACTER SET utf8 COLLATE utf8_general_ci;
),
)
}
///|
test "CREATE SCHEMA - Simple" {
let tokens = "CREATE SCHEMA myschema;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE SCHEMA myschema;
),
)
}
///|
test "CREATE SCHEMA IF NOT EXISTS" {
let tokens = "CREATE SCHEMA IF NOT EXISTS myschema;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE SCHEMA IF NOT EXISTS myschema;
),
)
}
///|
test "CREATE SCHEMA with AUTHORIZATION" {
let tokens = "CREATE SCHEMA myschema AUTHORIZATION myuser;"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE SCHEMA myschema AUTHORIZATION myuser;
),
)
}
///|
fn Parser::parse_create_function_statement(
self : Parser,
tokens : ArrayView[Token],
) -> ParserResult[CreateFunctionStmt] raise ParserError {
let tokens = self.expect_token(tokens, Keyword(Create))
let tokens = self.expect_token(tokens, Keyword(Function))
// Parse optional IF NOT EXISTS
let (if_not_exists, tokens) = match tokens {
[Keyword(If), Keyword(Not), Keyword(Exists), .. tokens] => (true, tokens)
_ => (false, tokens)
}
// Parse function name
let (name, tokens) = match tokens {
[Token::Identifier(name), .. tokens] => (name, tokens)
[Token::StringLiteral(name), .. tokens] => (name, tokens)
[token, .. _] => raise UnexpectedTokenError(token, Token::Identifier("function_name".to_string()))
[] => raise ParserError::InternalBug("parse_create_function_statement: unexpected end of tokens")
}
// Parse parameter list
let tokens = self.expect_token(tokens, LParen)
let parameters = []
let tokens = loop tokens {
[RParen, .. rest_tokens] => break rest_tokens
rest_tokens => {
let (param, rest_tokens) = self.parse_function_parameter(rest_tokens)
parameters.push(param)
match rest_tokens {
[Comma, .. rest_tokens] => continue rest_tokens
[RParen, .. rest_tokens] => break rest_tokens
[token, .. _] => raise UnexpectedTokenError(token, RParen)
[] => raise ParserError::InternalBug("parse_create_function_statement: unexpected end of tokens")
}
}
}
// Parse optional RETURNS clause
let (return_type, tokens) = match tokens {
[Keyword(Returns), .. tokens] => {
let (data_type, tokens) = self.parse_data_type(tokens)
(Some(data_type), tokens)
}
_ => (None, tokens)
}
// Parse optional LANGUAGE clause
let (language, tokens) = match tokens {
[Keyword(Language), Token::Identifier(lang), .. tokens] => (Some(lang), tokens)
[Keyword(Language), Token::StringLiteral(lang), .. tokens] => (Some(lang), tokens)
_ => (None, tokens)
}
// Parse optional DETERMINISTIC
let (deterministic, tokens) = match tokens {
[Keyword(Deterministic), .. tokens] => (true, tokens)
_ => (false, tokens)
}
// Parse optional body AS 'string'
let (body, tokens) = match tokens {
[Keyword(As), Token::StringLiteral(body_text), .. tokens] => (Some(body_text), tokens)
_ => (None, tokens)
}
({ name, parameters, return_type, language, body, deterministic, if_not_exists }, tokens)
}
///|
fn Parser::parse_create_procedure_statement(
self : Parser,
tokens : ArrayView[Token],
) -> ParserResult[CreateProcedureStmt] raise ParserError {
let tokens = self.expect_token(tokens, Keyword(Create))
let tokens = self.expect_token(tokens, Keyword(Procedure))
// Parse optional IF NOT EXISTS
let (if_not_exists, tokens) = match tokens {
[Keyword(If), Keyword(Not), Keyword(Exists), .. tokens] => (true, tokens)
_ => (false, tokens)
}
// Parse procedure name
let (name, tokens) = match tokens {
[Token::Identifier(name), .. tokens] => (name, tokens)
[Token::StringLiteral(name), .. tokens] => (name, tokens)
[token, .. _] => raise UnexpectedTokenError(token, Token::Identifier("procedure_name".to_string()))
[] => raise ParserError::InternalBug("parse_create_procedure_statement: unexpected end of tokens")
}
// Parse parameter list
let tokens = self.expect_token(tokens, LParen)
let parameters = []
let tokens = loop tokens {
[RParen, .. rest_tokens] => break rest_tokens
rest_tokens => {
let (param, rest_tokens) = self.parse_function_parameter(rest_tokens)
parameters.push(param)
match rest_tokens {
[Comma, .. rest_tokens] => continue rest_tokens
[RParen, .. rest_tokens] => break rest_tokens
[token, .. _] => raise UnexpectedTokenError(token, RParen)
[] => raise ParserError::InternalBug("parse_create_procedure_statement: unexpected end of tokens")
}
}
}
// Parse optional LANGUAGE clause
let (language, tokens) = match tokens {
[Keyword(Language), Token::Identifier(lang), .. tokens] => (Some(lang), tokens)
[Keyword(Language), Token::StringLiteral(lang), .. tokens] => (Some(lang), tokens)
_ => (None, tokens)
}
// Parse optional body AS 'string'
let (body, tokens) = match tokens {
[Keyword(As), Token::StringLiteral(body_text), .. tokens] => (Some(body_text), tokens)
_ => (None, tokens)
}
({ name, parameters, language, body, if_not_exists }, tokens)
}
///|
fn Parser::parse_function_parameter(
self : Parser,
tokens : ArrayView[Token],
) -> ParserResult[FunctionParameter] raise ParserError {
// Parse optional parameter mode (IN, OUT, INOUT)
let (mode, tokens) = match tokens {
[Keyword(In), .. tokens] => (Some(ParameterMode::In), tokens)
[Keyword(Out), .. tokens] => (Some(ParameterMode::Out), tokens)
[Keyword(InOut), .. tokens] => (Some(ParameterMode::InOut), tokens)
_ => (None, tokens)
}
// Parse parameter name
let (name, tokens) = match tokens {
[Token::Identifier(name), .. tokens] => (name, tokens)
[Token::StringLiteral(name), .. tokens] => (name, tokens)
[token, .. _] => raise UnexpectedTokenError(token, Token::Identifier("parameter_name".to_string()))
[] => raise ParserError::InternalBug("parse_function_parameter: unexpected end of tokens")
}
// Parse parameter type
let (param_type, tokens) = self.parse_data_type(tokens)
({ name, param_type, mode }, tokens)
}
///|
test "CREATE FUNCTION - Simple" {
let tokens = "CREATE FUNCTION my_func() RETURNS INTEGER AS 'SELECT 1';"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE FUNCTION my_func() RETURNS INTEGER AS 'SELECT 1';
),
)
}
///|
test "CREATE FUNCTION IF NOT EXISTS" {
let tokens = "CREATE FUNCTION IF NOT EXISTS my_func() RETURNS INTEGER AS 'SELECT 1';"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE FUNCTION IF NOT EXISTS my_func() RETURNS INTEGER AS 'SELECT 1';
),
)
}
///|
test "CREATE FUNCTION with parameters" {
let tokens = "CREATE FUNCTION add_nums(IN a INTEGER, IN b INTEGER) RETURNS INTEGER LANGUAGE sql AS 'SELECT a + b';"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE FUNCTION add_nums(IN a INTEGER, IN b INTEGER) RETURNS INTEGER LANGUAGE sql AS 'SELECT a + b';
),
)
}
///|
test "CREATE FUNCTION DETERMINISTIC" {
let tokens = "CREATE FUNCTION my_func() RETURNS INTEGER DETERMINISTIC AS 'SELECT 1';"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE FUNCTION my_func() RETURNS INTEGER DETERMINISTIC AS 'SELECT 1';
),
)
}
///|
test "CREATE PROCEDURE - Simple" {
let tokens = "CREATE PROCEDURE my_proc() AS 'BEGIN SELECT 1; END';"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE PROCEDURE my_proc() AS 'BEGIN SELECT 1; END';
),
)
}
///|
test "CREATE PROCEDURE with parameters" {
let tokens = "CREATE PROCEDURE update_user(IN user_id INTEGER, IN new_name VARCHAR(100)) LANGUAGE plpgsql AS 'BEGIN UPDATE users SET name = new_name WHERE id = user_id; END';"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE PROCEDURE update_user(IN user_id INTEGER, IN new_name VARCHAR(100)) LANGUAGE plpgsql AS 'BEGIN UPDATE users SET name = new_name WHERE id = user_id; END';
),
)
}
///|
test "CREATE FUNCTION with OUT parameter" {
let tokens = "CREATE FUNCTION get_user_info(IN user_id INTEGER, OUT user_name VARCHAR(100), OUT user_age INTEGER) AS 'SELECT name, age FROM users WHERE id = user_id';"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE FUNCTION get_user_info(IN user_id INTEGER, OUT user_name VARCHAR(100), OUT user_age INTEGER) AS 'SELECT name, age FROM users WHERE id = user_id';
),
)
}
///|
test "CREATE FUNCTION with INOUT parameter" {
let tokens = "CREATE FUNCTION modify_value(INOUT value INTEGER) RETURNS INTEGER AS 'SELECT value * 2';"
let stmt = parse_sql(tokens)[0] |> pretty_print
inspect(
stmt,
content=(
#|CREATE FUNCTION modify_value(INOUT value INTEGER) RETURNS INTEGER AS 'SELECT value * 2';
),
)
}