Fix typename parsing, set span on default constraint

This commit is contained in:
Simon Binder 2019-07-30 09:35:19 +02:00
parent b48970d9ef
commit 5df5e3cacc
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
2 changed files with 136 additions and 108 deletions

View File

@ -1,12 +1,5 @@
part of 'parser.dart';
const _tokensInTypename = [
TokenType.identifier,
TokenType.leftParen,
TokenType.rightParen,
TokenType.numberLiteral,
];
mixin SchemaParser on ParserBase {
CreateTableStatement _createTable() {
if (!_matchOne(TokenType.create)) return null;
@ -70,14 +63,7 @@ mixin SchemaParser on ParserBase {
final name = _consume(TokenType.identifier, 'Expected a column name')
as IdentifierToken;
final typeNameBuilder = StringBuffer();
while (_match(_tokensInTypename)) {
typeNameBuilder.write(_previous.lexeme);
}
final typeName =
typeNameBuilder.isEmpty ? null : typeNameBuilder.toString();
final typeName = _typeName();
final constraints = <ColumnConstraint>[];
ColumnConstraint constraint;
while ((constraint = _columnConstraint(orNull: true)) != null) {
@ -91,6 +77,33 @@ mixin SchemaParser on ParserBase {
)..setSpan(name, _previous);
}
String _typeName() {
// sqlite doesn't really define what a type name is and has very loose rules
// at turning them into a type affinity. We support this pattern:
// typename = identifier [ "(" { identifier | comma | number_literal } ")" ]
if (!_matchOne(TokenType.identifier)) return null;
final typeNameBuilder = StringBuffer(_previous.lexeme);
if (_matchOne(TokenType.leftParen)) {
typeNameBuilder.write('(');
const inBrackets = [
TokenType.identifier,
TokenType.comma,
TokenType.numberLiteral
];
while (_match(inBrackets)) {
typeNameBuilder..write(' ')..write(_previous.lexeme);
}
_consume(TokenType.rightParen,
'Expected closing paranthesis to finish type name');
}
return typeNameBuilder.toString();
}
ColumnConstraint _columnConstraint({bool orNull = false}) {
final first = _peek;
@ -127,7 +140,7 @@ mixin SchemaParser on ParserBase {
// when not a literal, expect an expression in parentheses
expr ??= _expressionInParentheses();
return Default(resolvedName, expr);
return Default(resolvedName, expr)..setSpan(first, _previous);
}
if (_matchOne(TokenType.collate)) {
final collation = _consumeIdentifier('Expected the collation name');

View File

@ -1,103 +1,118 @@
import 'package:sqlparser/sqlparser.dart';
import 'package:sqlparser/src/ast/ast.dart';
import 'package:test_core/test_core.dart';
import '../common_data.dart';
import 'utils.dart';
void main() {
testStatement(
createTableStmt,
CreateTableStatement(
tableName: 'users',
ifNotExists: true,
withoutRowId: false,
columns: [
ColumnDefinition(
columnName: 'id',
typeName: 'INT',
constraints: [
NotNull(null),
PrimaryKeyColumn(
null,
autoIncrement: true,
onConflict: ConflictClause.rollback,
mode: OrderingMode.descending,
),
],
),
ColumnDefinition(
columnName: 'email',
typeName: 'VARCHAR',
constraints: [
NotNull(null),
UniqueColumn(null, ConflictClause.abort),
],
),
ColumnDefinition(
columnName: 'score',
typeName: 'INT',
constraints: [
NotNull('score set'),
Default(null, NumericLiteral(420, token(TokenType.numberLiteral))),
CheckColumn(
null,
BinaryExpression(
Reference(columnName: 'score'),
token(TokenType.more),
NumericLiteral(
0,
token(TokenType.numberLiteral),
test('parsers simple create table statements', () {
testStatement(
'CREATE TABLE my_tbl (a INT, b TEXT)',
CreateTableStatement(tableName: 'my_tbl', columns: [
ColumnDefinition(columnName: 'a', typeName: 'INT'),
ColumnDefinition(columnName: 'b', typeName: 'TEXT'),
]),
);
});
test('parses complex CREATE TABLE statements', () {
testStatement(
createTableStmt,
CreateTableStatement(
tableName: 'users',
ifNotExists: true,
withoutRowId: false,
columns: [
ColumnDefinition(
columnName: 'id',
typeName: 'INT',
constraints: [
NotNull(null),
PrimaryKeyColumn(
null,
autoIncrement: true,
onConflict: ConflictClause.rollback,
mode: OrderingMode.descending,
),
],
),
ColumnDefinition(
columnName: 'email',
typeName: 'VARCHAR',
constraints: [
NotNull(null),
UniqueColumn(null, ConflictClause.abort),
],
),
ColumnDefinition(
columnName: 'score',
typeName: 'INT',
constraints: [
NotNull('score set'),
Default(
null, NumericLiteral(420, token(TokenType.numberLiteral))),
CheckColumn(
null,
BinaryExpression(
Reference(columnName: 'score'),
token(TokenType.more),
NumericLiteral(
0,
token(TokenType.numberLiteral),
),
),
),
),
],
),
ColumnDefinition(
columnName: 'display_name',
typeName: 'VARCHAR',
constraints: [
CollateConstraint(
null,
'BINARY',
),
ForeignKeyColumnConstraint(
null,
ForeignKeyClause(
foreignTable: TableReference('some', null),
columnNames: [Reference(columnName: 'thing')],
onUpdate: ReferenceAction.cascade,
onDelete: ReferenceAction.setNull,
),
),
],
)
],
tableConstraints: [
KeyClause(
null,
isPrimaryKey: false,
indexedColumns: [
Reference(columnName: 'score'),
Reference(columnName: 'display_name'),
],
onConflict: ConflictClause.abort,
),
ForeignKeyTableConstraint(
null,
columns: [
Reference(columnName: 'id'),
Reference(columnName: 'email'),
],
clause: ForeignKeyClause(
foreignTable: TableReference('another', null),
columnNames: [
Reference(columnName: 'a'),
Reference(columnName: 'b'),
],
onDelete: ReferenceAction.noAction,
onUpdate: ReferenceAction.restrict,
),
)
],
),
);
ColumnDefinition(
columnName: 'display_name',
typeName: 'VARCHAR',
constraints: [
CollateConstraint(
null,
'BINARY',
),
ForeignKeyColumnConstraint(
null,
ForeignKeyClause(
foreignTable: TableReference('some', null),
columnNames: [Reference(columnName: 'thing')],
onUpdate: ReferenceAction.cascade,
onDelete: ReferenceAction.setNull,
),
),
],
)
],
tableConstraints: [
KeyClause(
null,
isPrimaryKey: false,
indexedColumns: [
Reference(columnName: 'score'),
Reference(columnName: 'display_name'),
],
onConflict: ConflictClause.abort,
),
ForeignKeyTableConstraint(
null,
columns: [
Reference(columnName: 'id'),
Reference(columnName: 'email'),
],
clause: ForeignKeyClause(
foreignTable: TableReference('another', null),
columnNames: [
Reference(columnName: 'a'),
Reference(columnName: 'b'),
],
onDelete: ReferenceAction.noAction,
onUpdate: ReferenceAction.restrict,
),
)
],
),
);
});
}