jaypipes / sqltoast Goto Github PK
View Code? Open in Web Editor NEWA SQL parser written in C++
License: Apache License 2.0
A SQL parser written in C++
License: Apache License 2.0
The sqltoast::statements::create_schema
class is defined thusly:
typedef struct create_schema : statement_t {
identifier_t schema_identifier;
std::unique_ptr<identifier_t> authorization_identifier;
std::unique_ptr<identifier_t> default_charset;
create_schema(
identifier_t& schema_id,
std::unique_ptr<identifier_t>& authorization_id,
std::unique_ptr<identifier_t>& default_charset_id) :
statement_t(STATEMENT_TYPE_CREATE_SCHEMA),
schema_identifier(schema_id),
authorization_identifier(std::move(authorization_id)),
default_charset(std::move(default_charset_id))
{}
virtual const std::string to_string();
} create_schema_t;
and its to_string()
method is defined thusly:
const std::string create_schema::to_string() {
std::stringstream ss;
ss << "<statement: CREATE SCHEMA" << std::endl
<< " schema identifier: " << schema_identifier;
if (authorization_identifier.get()) {
ss << std::endl
<< " authorization identifier: " << *authorization_identifier;
}
if (default_charset.get()) {
ss << std::endl
<< " default charset: " << *default_charset;
}
ss << ">" << std::endl;
return ss.str();
}
Unfortunately, the above always prints out the authorization element (as "") and the default character set element (also as "") regardless of whether the parser actually finds those clauses.
Figure out why.
Currently, only numeric literals and identifiers are parsed as members of the clause of the INSERT INTO ... VALUES statement:
$ ./sqltoaster "INSERT INTO t1 VALUES (CURRENT_DATE)"
Syntax error.
Expected a value item, but got keyword[CURRENT_DATE].
INSERT INTO t1 VALUES (CURRENT_DATE)
^^^^^^^^^^^^^^
the above should produce a columns list of one value expression of type VALUE_EXPRESSION_TYPE_DATETIME_EXPRESSION
The EXISTS predicate is part of the ANSI SQL-02 standard and is fairly common to see in real life.
It's grammar looks like this:
<exists predicate> ::= EXISTS <table subquery>
Left support for the CASE expressions out of 0.1 milestone/release. Would be good to support them:
<case expression> ::= <case abbreviation> | <case specification>
<case abbreviation> ::=
NULLIF <left paren> <value expression> <comma> <value expression> <right paren>
| COALESCE <left paren> <value expression> { <comma> <value expression> }... <right paren>
<case specification> ::= <simple case> | <searched case>
<simple case> ::=
CASE <case operand>
<simple when clause> ...
[ <else clause> ]
END
<case operand> ::= <value expression>
<simple when clause> ::= WHEN <when operand> THEN <result>
<when operand> ::= <value expression>
<result> ::= <result expression> | NULL
<result expression> ::= <value expression>
<else clause> ::= ELSE <result>
<searched case> ::=
CASE
<searched when clause> ...
[ <else clause> ]
END
<searched when clause> ::= WHEN <search condition> THEN <result>
The ANSI not equals operator is actually "<>", not "!=". Support it as a single symbol just like the double-char concatenation operator ("||").
There is now a sqltoast::parse_options_t
switch that disables the construction of the sqltoast::statement
objects if the caller doesn't want or need these objects.
One reason the caller may not want the objects is because they actually aren't attempting to execute operations using the information in the objects but rather need a simple top-level routing parser that can indicate the "category" of SQL statement that has been sent. For example, a top-level proxy/router might want to simply know whether the successfully parsed statement is a DDL statement (CREATE, DROP, SET, etc) or a DML statement (INSERT, SELECT, DELETE, UPDATE etc).
Further, such a proxy might want to know a sub-category of the DDL or DML statement, for instance to direct read operations to different backing endpoints than write operations.
In these use cases, there's no point in constructing the sqltoast::statement
objects or even validating much more than the rough tokens corresponding to the SQL statement are valid. Instead, there should be a sqltoast::parse_options_t
field to trigger short-circuiting parsing when the caller is only interested in the category of statement or subcategory.
The OVERLAPS predicate is also very rarely seen in real life, but I'd still like to support it considering it's part of the ANSI SQL-92 standard. Its grammar looks like this:
<overlaps predicate> ::= <row value constructor 1> OVERLAPS <row value constructor 2>
<row value constructor 1> ::= <row value constructor>
<row value constructor 2> ::= <row value constructor>
Due the various things, including how the lexer's cursor is moved over time, sometimes the syntax errors produced when a parsing problem happens can be confusing. Por ejemplo:
./sqltoaster/sqltoaster "SELECT NULLIF(a, b) FROM t1"
OK
statements[0]:
<statement: SELECT
selected columns:
0: nullif[column-reference[a],column-reference[b]]
referenced tables:
0: t1>
(took 73006 nanoseconds)
If I remove the a,b arguments accidentally, I get the following parser syntax error:
./sqltoaster/sqltoaster "SELECT NULLIF() FROM t1"
Syntax error.
Expected to find one of ('*'|<< identifier >>) but found symbol[')']
SELECT NULLIF() FROM t1
^^^^^^^^^^
(took 78092 nanoseconds)
What should be there is a message about how a is expected as the first argument to the NULLIF() function.
Quantified comparison predicates look like this:
SELECT * FROM t2
WHERE t2.t1_a > ANY (SELECT t1.a FROM t1)
It's not particularly common to see it used any more versus the now-more-standard JOIN expression that would be functionally the same:
SELECT t2.* FROM t2
JOIN t1
ON t2.t1_a > t1.a
But nevertheless, it's in the ANSI SQL-92 standard and I'd like to support the majority of the ANSI SQL-92 grammar.
<quantified comparison predicate> ::= <row value constructor> <comp op> <quantifier> <table subquery>
<quantifier> ::= <all> | <some>
<all> ::= ALL
<some> ::= SOME | ANY
Often used in VALUES() lists:
INSERT INTO <table> VALUES (DEFAULT)
Obviously, this is a huge task, broken out into many subtasks/subissues.
The DROP TABLE
statement is identical to the DROP SCHEMA
SQL statement
A good developer reference would be great now that the code is a) consistent and b) relatively full-featured.
Syntax error.
Expected to find ')' but found punctuator['=']
SELECT * FROM t1 WHERE a = 1 AND (b = 2 OR c = 3)
^^^^^^^^^^^^^^
(took 76613 nanoseconds)
Column definition elements have a data type component, which has the following grammar:
<data type> ::=
<character string type> [ CHARACTER SET <character set specification> ]
| <national character string type>
| <bit string type>
| <numeric type>
| <datetime type>
| <interval type>
<character string type> ::=
CHARACTER [ <left paren> <length> <right paren> ]
| CHAR [ <left paren> <length> <right paren> ]
| CHARACTER VARYING [ <left paren> <length> <right paren> ]
| CHAR VARYING [ <left paren> <length> <right paren> ]
| VARCHAR [ <left paren> <length> <right paren> ]
<length> ::= <unsigned integer>
<national character string type> ::=
NATIONAL CHARACTER [ <left paren> <length> <right paren> ]
| NATIONAL CHAR [ <left paren> <length> <right paren> ]
| NCHAR [ <left paren> <length> <right paren> ]
| NATIONAL CHARACTER VARYING [ <left paren> <length> <right paren> ]
| NATIONAL CHAR VARYING [ <left paren> <length> <right paren> ]
| NCHAR VARYING [ <left paren> <length> <right paren> ]
<bit string type> ::=
BIT [ <left paren> <length> <right paren> ]
| BIT VARYING [ <left paren> <length> <right paren> ]
<numeric type> ::=
<exact numeric type>
| <approximate numeric type>
<exact numeric type> ::=
NUMERIC [ <left paren> <precision> [ <comma> <scale> ] <right paren> ]
| DECIMAL [ <left paren> <precision> [ <comma> <scale> ] <right paren> ]
| DEC [ <left paren> <precision> [ <comma> <scale> ] <right paren> ]
| INTEGER
| INT
| SMALLINT
<precision> ::= <unsigned integer>
<scale> ::= <unsigned integer>
<approximate numeric type> ::=
FLOAT [ <left paren> <precision> <right paren> ]
| REAL
| DOUBLE PRECISION
<datetime type> ::=
DATE
| TIME [ <left paren> <time precision> <right paren> ] [ WITH TIME ZONE ]
| TIMESTAMP [ <left paren> <timestamp precision> <right paren> ] [ WITH TIME ZONE ]
<time precision> ::= <time fractional seconds precision>
<time fractional seconds precision> ::= <unsigned integer>
<timestamp precision> ::= <time fractional seconds precision>
<interval type> ::= INTERVAL <interval qualifier>
<interval qualifier> ::=
<start field> TO <end field>
| <single datetime field>
<start field> ::=
<non-second datetime field> [ <left paren> <interval leading field precision> <right paren> ]
<non-second datetime field> ::= YEAR | MONTH | DAY | HOUR | MINUTE
<interval leading field precision> ::= <unsigned integer>
<end field> ::=
<non-second datetime field>
| SECOND [ <left paren> <interval fractional seconds precision> <right paren> ]
<interval fractional seconds precision> ::= <unsigned integer>
<single datetime field> ::=
<non-second datetime field> [ <left paren> <interval leading field precision> <right paren> ]
| SECOND [ <left paren> <interval leading field precision> [ <comma> <left paren> <interval fractional seconds precision> ] <right paren> ]
<domain name> ::= <qualified name>
<qualified name> ::= [ <schema name> <period> ] <qualified identifier>
<default clause> ::= DEFAULT <default option>
<default option> ::=
<literal>
| <datetime value function>
| USER
| CURRENT_USER
| SESSION_USER
| SYSTEM_USER
| NULL
Statements like CREATE TABLE
and ALTER TABLE
have sub-elements that represent column definitions. The grammar for a column definition looks like this:
<column definition> ::=
<column name> { <data type> | <domain name> } [ <default clause> ] [ <column constraint definition> ... ] [ <collate clause> ]
<column name> ::= <identifier>
Parse the DELETE
SQL statement
jaypipes@uberbox:~/src/github.com/jaypipes/sqltoast/_build$ ./sqltoaster "CREATE TABLE t1 (a INT DEFAULT CURRENT_USER)"
OK
statements[0]:
<statement: CREATE TABLE
table name: t1
column definitions:
a INT DEFAULT CURRENT_USER>
(took 27271 nanoseconds)
jaypipes@uberbox:~/src/github.com/jaypipes/sqltoast/_build$ ./sqltoaster "CREATE TABLE t1 (a CHAR DEFAULT CURRENT_USER)"
Syntax error.
Expected to find << unsigned integer >> but found keyword[DEFAULT]
CREATE TABLE t1 (a CHAR DEFAULT CURRENT_USER)
^^^^^^^^^^^^^^^^^^^^^^
(took 32551 nanoseconds)
jaypipes@uberbox:~/src/github.com/jaypipes/sqltoast/_build$ ./sqltoaster "CREATE TABLE t1 (a CHAR(20) DEFAULT CURRENT_USER)"
Syntax error.
Expected to find << unsigned integer >> but found symbol['(']
CREATE TABLE t1 (a CHAR(20) DEFAULT CURRENT_USER)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
(took 29094 nanoseconds)
From what I can tell, lex.next()
isn't being called properly or at the right times after successfully parsing delimiter tokens.
Often used in VALUES lists:
INSERT INTO <table> VALUES (NULL)
Demonstrating the bug here:
$ ./sqltoaster "create tablea"
OK
statements[0]:
<statement: CREATE TABLE
table identifier: a>
(took 17564 nanoseconds)
That should actually result in a parse failure because "tablea" is not actually a keyword. "table" is, not "tablea". However, the sqltoast::token_keyword()
function is not properly picking up that the symbol matching should be exact-lengthed.
The GRANT
SQL statement has the following EBNF grammar:
<grant statement> ::=
GRANT <privileges> ON <object name> TO <grantee> [ { <comma> <grantee> }... ] [ WITH GRANT OPTION ]
<privileges> ::= ALL PRIVILEGES | <action list>
<action list> ::= <action> [ { <comma> <action> }... ]
<action> ::=
SELECT
| DELETE
| INSERT [ <left paren> <privilege column list> <right paren> ]
| UPDATE [ <left paren> <privilege column list> <right paren> ]
| REFERENCES [ <left paren> <privilege column list> <right paren> ]
| USAGE
<privilege column list> ::= <column name list>
<object name> ::=
[ TABLE ] <table name>
| DOMAIN <domain name>
| COLLATION <collation name>
| CHARACTER SET <character set name>
| TRANSLATION <translation name>
<grantee> ::= PUBLIC | <authorization identifier>
Need to support the UNION
operation which combines the results of two selections
In order to support recursively calling certain statement parsers (such as parse_select()), we need to convert them to accepting a std::unique_ptr<statement_t>
out target for the parser to hang the potential statement from instead of always appending the parsed statement_t to the ctx.statements vector.
Numeric factors can be either value expression primaries or one of the following numeric functions:
EXTRACT
POSITION
CHAR_LENGTH
or CHARACTER_LENGTH
OCTET_LENGTH
BIT_LENGTH
ANSI-92 does not have a BEGIN
statement. Instead, it only has a COMMIT [WORK]
and ROLLBACK [WORK]
statement.
Set up some travis-ci.org jobs for building the library and sqltoaster binary and then executing the test suite.
Parse the UPDATE
SQL statement
A lexeme_t
struct, which is 16 bytes in size on 64-bit architecture (2 pointers to char) is really all we need to store when we come across identifiers in the tokenization process. Currently, however, we construct identifier_t
structs from a lexeme_t
struct and construct a std::string by copy constructor in the identifier_t
constructor:
typedef struct identifier {
const std::string name;
identifier(lexeme_t& lexeme) : name(lexeme.start, lexeme.end)
{}
} identifier_t;
I don't believe we should do this, however. Instead, we should just store the lexeme_t directly in the parse tree structures and create identifier_t objects on-demand if needed.
For example, if we take a look at the definition of the column_definition_t
struct:
typedef struct column_definition {
identifier_t id;
bool is_nullable;
std::unique_ptr<data_type_descriptor_t> data_type;
std::unique_ptr<default_descriptor_t> default_descriptor;
std::unique_ptr<identifier_t> collate;
column_definition(identifier_t& id) :
id(id),
is_nullable(true)
{}
} column_definition_t;
we see that there is an "id" field of type identifier_t
. We construct this identifier by copy construction in the column_definition_t
constructor, which will construct a new std::string object. We don't actually need to do this, though, and it's a waste of memory since all we really need to store in the id field is the demarcation points for where the column name is. The lexeme_t
is the struct that has this demarcation information so we should just use a lexeme_t
as the type of "id".
Currently, the sqltoast::tokenize()
function performs tokenization on the entire input subject, adding found tokens to a std::deque<token_t>
that is embedded in the parse_context_t
struct.
In doing some initial performance analysis of the sqltoast::parse()
I noted that at least 70% of the time the function is spent in the sqltoast::tokenize()
function. The act of pushing token_t objects into the ctx.tokens std::deque is time-consuming and consumes heap allocated memory when most of the parsing functions really just need to examine a small stack of tokens.
The ALTER TABLE
statement uses the following EBNF grammar:
<alter table statement> ::= ALTER TABLE <table name> <alter table action>
<alter table action> ::=
<add column definition>
| <alter column definition>
| <drop column definition>
| <add table constraint definition>
| <drop table constraint definition>
<add column definition> ::= ADD [ COLUMN ] <column definition>
<alter column definition> ::= ALTER [ COLUMN ] <column name> <alter column action>
<alter column action> ::= <set column default clause> | <drop column default clause>
<set column default clause> ::= SET <default clause>
<drop column default clause> ::= DROP DEFAULT
<drop column definition> ::= DROP [ COLUMN ] <column name> <drop behaviour>
<add table constraint definition> ::= ADD <table constraint definition>
<drop table constraint definition> ::= DROP CONSTRAINT <constraint name> <drop behaviour>
jaypipes@uberbox:~/src/github.com/jaypipes/sqltoast/_build$ ./sqltoaster "select * from t1"
OK
statements[0]:
<statement: SELECT
selected columns:
0: literal[*]
referenced tables:
0: t1>
(took 9172 nanoseconds)
jaypipes@uberbox:~/src/github.com/jaypipes/sqltoast/_build$ ./sqltoaster "select * from t1_under"
Syntax error.
Expected to find one of (EOS|';'|')') but found identifier[t1]
select * from t1_under
^^^^^^^^^
(took 28577 nanoseconds)
This demonstrates the issue:
jaypipes@uberbox:~/src/github.com/jaypipes/sqltoast/_build$ ./sqltoaster "SELECT * FROM t1 WHERE a = 1 OR b = 2"
OK
statements[0]:
<statement: SELECT
selected columns:
0: *
referenced tables:
0: t1
where conditions:
column-reference[a] = literal[1]
OR
column-reference[b] = literal[2]>
(took 14494 nanoseconds)
jaypipes@uberbox:~/src/github.com/jaypipes/sqltoast/_build$ ./sqltoaster "SELECT * FROM t1 WHERE a = 1 OR b = 2 OR c = 3"
OK
statements[0]:
<statement: SELECT
selected columns:
0: *
referenced tables:
0: t1
where conditions:
column-reference[a] = literal[1]
OR
column-reference[b] = literal[2]
OR
column-reference[c] = literal[3]
OR
column-reference[c] = literal[3]>
(took 18825 nanoseconds)
jaypipes@uberbox:~/src/github.com/jaypipes/sqltoast/_build$ ./sqltoaster "SELECT * FROM t1 WHERE a = 1 OR b = 2 OR c = 3 OR d = 4"
OK
statements[0]:
<statement: SELECT
selected columns:
0: *
referenced tables:
0: t1
where conditions:
column-reference[a] = literal[1]
OR
column-reference[b] = literal[2]
OR
column-reference[c] = literal[3]
OR
column-reference[d] = literal[4]
OR
column-reference[d] = literal[4]
OR
column-reference[c] = literal[3]
OR
column-reference[d] = literal[4]
OR
column-reference[d] = literal[4]>
(took 17427 nanoseconds)
Part of Issue #30. Need to parse explicit JOIN clauses.
Currently, only unsigned numeric value expressions are being parsed.
<value expression> ::=
<numeric value expression>
| <string value expression>
| <datetime value expression>
| <interval value expression>
jaypipes@uberbox:~/src/github.com/jaypipes/sqltoast/_build$ ./sqltoaster "select distinct t1.a FROM t1 WHERE a = 21"
Syntax error.
Expected to find << identifier >> but found literal[2]
select distinct t1.a FROM t1 WHERE a = 21
^^^
(took 199640 nanoseconds)
jaypipes@uberbox:~/src/github.com/jaypipes/sqltoast/_build$ ./sqltoaster "select distinct t1.a FROM t1 WHERE a = n"
OK
statements[0]:
<statement: SELECT
distinct: true
selected columns:
0: t1.a
referenced tables:
0: t1
where conditions:
0: a = n1>
(took 280725 nanoseconds)
jaypipes@uberbox:~/src/github.com/jaypipes/sqltoast/_build$ ./sqltoaster "select distinct t1.a FROM t1 WHERE a = n1"
OK
statements[0]:
<statement: SELECT
distinct: true
selected columns:
0: t1.a
referenced tables:
0: t1
where conditions:
0: a = n1>
(took 279234 nanoseconds)
jaypipes@uberbox:~/src/github.com/jaypipes/sqltoast/_build$ ./sqltoaster "select distinct t1.a FROM t1 WHERE a = n12"
OK
statements[0]:
<statement: SELECT
distinct: true
selected columns:
0: t1.a
referenced tables:
0: t1
where conditions:
0: a = n12>
(took 281246 nanoseconds)
The UNIQUE predicate is part of the ANSI SQL-02 standard and is not particularly common to see in real life. However, it is part of the standard and would be good to support it, considering it has a simple grammar:
<unique predicate> ::= UNIQUE <table subquery>
Currently, support for literal tokens is limited to numerics (exact and approximate). We need to support string, datetime and interval literals as well.
support for string literals
support for datetime literals
support for interval literals
Support both the INSERT ... VALUES
and INSERT SELECT
variants.
The SET
SQL statement is a generic/administrative action statement to set variables and things like the transaction isolation level.
tests should plaintext files in the form of:
# Comments begin with a hash, end in a newline
# Blank lines are ignored.
# SQL statements are placed directly into the test file.
# The string representation of the AST shall come
# directly after the SQL, delimited by the markers
# [AST] and [/AST] separated by newlines:
CREATE DATABASE mydb;
[AST]
<begin> -> <CREATE DATABASE statement> -> <identifier> -> <end>
[/AST]
A simple test runner should be able to be written quickly the reads the non-comment lines and passes the input string to sqltoast::parse()
, verifies that sqltoast::parse_result::code
== SUCCESS
and then calls sqltoast::ast_to_string()
on the sqltoast::parse_result.ast
and does a simple string assertion of the difference, if any.
./sqltoaster "CREATE TABLE t1 (a INT DEFAULT -1)"
Syntax error.
Expected either a column definition or a constraint but found symbol['-']
CREATE TABLE t1 (a INT DEFAULT -1)
^^^^
(took 34494 nanoseconds)
The CREATE TABLE
statement has the following EBNF grammar (for ANSI-92 SQL):
<table definition> ::=
CREATE [ { GLOBAL | LOCAL } TEMPORARY ] TABLE <table name> <table element list> [ ON COMMIT { DELETE | PRESERVE } ROWS ]
<table element list> ::= <left paren> <table element> [ { <comma> <table element> }... ] <right paren>
<table element> ::= <column definition> | <table constraint definition>
<column definition> ::=
<column name> { <data type> | <domain name> } [ <default clause> ] [ <column constraint definition> ... ] [ <collate clause> ]
<column name> ::= <identifier>
<column constraint definition> ::=
[ <constraint name definition> ] <column constraint> [ <constraint attributes> ]
<constraint name definition> ::= CONSTRAINT <constraint name>
<constraint name> ::= <qualified name>
<column constraint> ::=
NOT NULL
| <unique specification>
| <references specification>
| <check constraint definition>
<unique specification> ::= UNIQUE | PRIMARY KEY
<references specification> ::=
REFERENCES <referenced table and columns> [ MATCH <match type> ] [ <referential triggered action> ]
<referenced table and columns> ::= <table name> [ <left paren> <reference column list> <right paren> ]
<table name> ::= <qualified name> | <qualified local table name>
<reference column list> ::= <column name list>
<column name list> ::= <column name> [ { <comma> <column name> }... ]
<match type> ::= FULL | PARTIAL
<referential triggered action> ::=
<update rule> [ <delete rule> ]
| <delete rule> [ <update rule> ]
<update rule> ::= ON UPDATE <referential action>
<referential action> ::= CASCADE | SET NULL | SET DEFAULT | NO ACTION
<delete rule> ::= ON DELETE <referential action>
<check constraint definition> ::= CHECK <left paren> <search condition> <right paren>
Currently, only unsigned numeric value expressions are being parsed.
<value expression> ::=
<numeric value expression>
| <string value expression>
| <datetime value expression>
| <interval value expression>
In order to determine where sqltoast is spending its time during tokenization and parsing efforts, I'd like to provide full instrumentation of all primary functions. The instrumentation "interface" should look something like this:
#include <iostream>
#include <cctype>
#include <sqltoast.h>
sqltoast::parse_input_t subject("CREATE SCHEMA s1");
sqltoast::parse_options_t opts;
opts.enable_instrumentation = true;
auto res = sqltoast::parse(subject, opts);
for (auto instr = res.instrumentation) {
std::cout << instr.block << ": " << instr.total_ns << " nanoseconds" << std::endl;
}
which would output something like this:
tokenize(): 31880 nanoseconds
-> token_comment(): 678 nanoseconds
-> token_keyword(): 23449 nanoseconds
-> token_identifier(): 6777 nanoseconds
parse_statement(): 1156 nanoseconds
-> parse_create_schema(): 928 nanoseconds
The MATCH predicate is not commonly used any more but is part of the ANSI SQL-92 standard and so I'd like to support it, for competeness. Its grammar looks like so:
<match predicate> ::= <row value constructor> MATCH [ UNIQUE ] [ PARTIAL | FULL ] <table subquery>
./sqltoaster "CREATE TABLE t1 (a INT DEFAULT 1)"
OK
statements[0]:
<statement: CREATE TABLE
table name: t1
column definitions:
a INT DEFAULT ')'>
(took 18826 nanoseconds)
Support CHECK constraints in both column and table definitions.
The NULL constraint specifier is default for the CREATE TABLE
statement, however we don't support it. We only support the standard NOT NULL
constraint specifier:
jaypipes@uberbox:~/src/github.com/jaypipes/sqltoast/_build$ ./sqltoaster "create table t1 (c1 int not null primary key)"
OK
statements[0]:
<statement: CREATE TABLE
table identifier: t1
column definitions:
c1 INT NOT NULL
constraints:
PRIMARY KEY(c1)>
(took 28831 nanoseconds)
jaypipes@uberbox:~/src/github.com/jaypipes/sqltoast/_build$ ./sqltoaster "create table t1 (id int not null primary key, name varchar(200) null)"
Syntax error.
Expected to find one of (','|')') but found keyword[NULL]
create table t1 (id int not null primary key, name varchar(200) null)
^^^^^^
(took 61148 nanoseconds)
Should be a simple fix to ignore the NULL constraint specifier when NOT isn't present.
<numeric value expression> ::=
<term>
| <numeric value expression> <plus sign> <term>
| <numeric value expression> <minus sign> <term>
<term> ::=
<factor>
| <term> <asterisk> <factor>
| <term> <solidus> <factor>
<factor> ::= [ <sign> ] <numeric primary>
<numeric primary> ::= <value expression primary> | <numeric value function>
Handle datetime value expressions, which are fairly complicated and related to interval value expressions and numeric value expressions.
<datetime value expression> ::=
<datetime term>
| <interval value expression> <plus sign> <datetime term>
| <datetime value expression> <plus sign> <interval term>
| <datetime value expression> <minus sign> <interval term>
<interval term> ::=
<interval factor>
| <interval term 2> <asterisk> <factor>
| <interval term 2> <solidus> <factor>
| <term> <asterisk> <interval factor>
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.