Parse "STRICT" table definitions

This commit is contained in:
Simon Binder 2021-08-28 16:28:29 +02:00
parent 6dc471a4ce
commit 074b663b79
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
8 changed files with 99 additions and 12 deletions

View File

@ -28,8 +28,16 @@ abstract class TableInducingStatement extends Statement
class CreateTableStatement extends TableInducingStatement {
List<ColumnDefinition> columns;
List<TableConstraint> tableConstraints;
/// Whether this table has been defined with an `WITHOUT ROWID` clause.
final bool withoutRowId;
/// Whether this table has been defined as `STRICT`.
///
/// Strict tables are limited to a few column type names. Columns in strict
/// tables may not store other types.
final bool isStrict;
Token? openingBracket;
Token? closingBracket;
@ -39,6 +47,7 @@ class CreateTableStatement extends TableInducingStatement {
this.columns = const [],
this.tableConstraints = const [],
this.withoutRowId = false,
this.isStrict = false,
MoorTableName? moorTableName,
}) : super._(ifNotExists, tableName, moorTableName);

View File

@ -67,13 +67,16 @@ class SqliteVersion implements Comparable<SqliteVersion> {
/// can't provide analysis warnings when using recent sqlite3 features.
static const SqliteVersion minimum = SqliteVersion.v3(34);
/// Version `3.37.0` of `sqlite3`.
static const SqliteVersion v3_37 = SqliteVersion.v3(37);
/// Version `3.35.0` of `sqlite3`.
static const SqliteVersion v3_35 = SqliteVersion.v3(35);
/// The highest sqlite version supported by this `sqlparser` package.
///
/// Newer features in `sqlite3` may not be recognized by this library.
static const SqliteVersion current = SqliteVersion.v3(36);
static const SqliteVersion current = v3_37;
/// The major version of sqlite.
///

View File

@ -1885,10 +1885,41 @@ class Parser {
_consume(TokenType.rightParen, 'Expected closing parenthesis');
var withoutRowId = false;
if (_matchOne(TokenType.without)) {
_consume(
TokenType.rowid, 'Expected ROWID to complete the WITHOUT ROWID part');
withoutRowId = true;
var isStrict = false;
// Parses a `WITHOUT ROWID` or a `STRICT` keyword. Returns if either such
// option has been parsed.
bool tableOptions() {
if (_matchOne(TokenType.strict)) {
isStrict = true;
return true;
} else if (_matchOne(TokenType.without)) {
_consume(TokenType.rowid,
'Expected ROWID to complete the WITHOUT ROWID part');
withoutRowId = true;
return true;
}
return false;
}
// Table options can be seperated by comma, but they're not required either.
if (tableOptions()) {
while (_matchOne(TokenType.comma)) {
if (!tableOptions()) {
_error('Expected WITHOUT ROWID or STRICT here!');
}
}
}
while (_check(TokenType.without) || _check(TokenType.strict)) {
if (_matchOne(TokenType.without)) {
} else {
// Matched a strict keyword
isStrict = true;
_advance();
assert(_previous.type == TokenType.strict);
}
}
final overriddenName = _moorTableName();
@ -1899,6 +1930,7 @@ class Parser {
withoutRowId: withoutRowId,
columns: columns,
tableConstraints: tableConstraints,
isStrict: isStrict,
moorTableName: overriddenName,
)
..setSpan(first, _previous)

View File

@ -167,6 +167,7 @@ enum TokenType {
shiftRight,
slash,
star,
strict,
stringLiteral,
table,
temp,
@ -329,6 +330,7 @@ const Map<String, TokenType> keywords = {
'SAVEPOINT': TokenType.savepoint,
'SELECT': TokenType.select,
'SET': TokenType.set,
'STRICT': TokenType.strict,
'TABLE': TokenType.table,
'TEMP': TokenType.temp,
'TEMPORARY': TokenType.temporary,

View File

@ -206,7 +206,8 @@ class EqualityEnforcingVisitor implements AstVisitor<void, void> {
_assert(
current.ifNotExists == e.ifNotExists &&
current.tableName == e.tableName &&
current.withoutRowId == e.withoutRowId,
current.withoutRowId == e.withoutRowId &&
current.isStrict == e.isStrict,
e);
_checkChildren(e);
}

View File

@ -393,6 +393,12 @@ class NodeSqlBuilder extends AstVisitor<void, void> {
_keyword(TokenType.without);
_keyword(TokenType.rowid);
}
if (e.isStrict) {
if (e.withoutRowId) _symbol(',');
_keyword(TokenType.strict);
}
}
@override

View File

@ -251,4 +251,18 @@ void main() {
),
);
});
test('parses WITHOUT ROWID and STRICT', () {
testStatement(
'CREATE TABLE a (c INTEGER) STRICT, WITHOUT ROWID, STRICT',
CreateTableStatement(
tableName: 'a',
columns: [
ColumnDefinition(columnName: 'c', typeName: 'INTEGER'),
],
withoutRowId: true,
isStrict: true,
),
);
});
}

View File

@ -94,8 +94,9 @@ CREATE VIEW my_view AS SELECT * FROM t1;
''');
});
test('table', () {
testFormat('''
group('table', () {
test('complex', () {
testFormat('''
CREATE TABLE IF NOT EXISTS my_table(
foo TEXT NOT NULL PRIMARY KEY DEFAULT (3 * 4),
baz INT CONSTRAINT not_null NOT NULL PRIMARY KEY AUTOINCREMENT,
@ -114,16 +115,35 @@ CREATE TABLE IF NOT EXISTS my_table(
FOREIGN KEY (bar) REFERENCES t2 (bax) ON DELETE SET DEFAULT NOT DEFERRABLE
);
''');
});
testFormat('''
test('WITHOUT ROWID', () {
testFormat('''
CREATE TABLE IF NOT EXISTS my_table(
foo INTEGER NOT NULL PRIMARY KEY ASC
) WITHOUT ROWID;
''');
});
});
test('virtual table', () {
testFormat('CREATE VIRTUAL TABLE foo USING bar(a, b, c);');
test('STRICT', () {
testFormat('''
CREATE TABLE IF NOT EXISTS my_table(
foo INTEGER NOT NULL PRIMARY KEY ASC
) STRICT;
''');
});
test('STRICT and WITHOUT ROWID', () {
testFormat('''
CREATE TABLE IF NOT EXISTS my_table(
foo INTEGER NOT NULL PRIMARY KEY ASC
) WITHOUT ROWID, STRICT;
''');
});
test('virtual', () {
testFormat('CREATE VIRTUAL TABLE foo USING bar(a, b, c);');
});
});
test('index', () {