mirror of https://github.com/AMT-Cheif/drift.git
Enforce text length, escape table and column names
This commit is contained in:
parent
c9aab2e824
commit
5909b0d3a2
|
@ -103,7 +103,6 @@ create an issue.
|
|||
- Custom primary keys
|
||||
- Stabilize all end-user APIs and document them extensively
|
||||
- Support default values and expressions, auto-increment
|
||||
- Escape table and column names
|
||||
- Auto-updating streams for select statements
|
||||
##### Definitely planned for the future
|
||||
- Allow using DAOs instead of having to put everything in the main database
|
||||
|
|
|
@ -25,6 +25,7 @@ abstract class GeneratedColumn<T, S extends SqlType<T>> extends Column<T, S> {
|
|||
|
||||
@visibleForOverriding
|
||||
void writeCustomConstraints(StringBuffer into) {}
|
||||
|
||||
@visibleForOverriding
|
||||
String get typeName;
|
||||
|
||||
|
@ -54,7 +55,12 @@ abstract class GeneratedColumn<T, S extends SqlType<T>> extends Column<T, S> {
|
|||
|
||||
class GeneratedTextColumn extends GeneratedColumn<String, StringType>
|
||||
implements TextColumn {
|
||||
GeneratedTextColumn(String name, bool nullable) : super(name, nullable);
|
||||
final int minTextLength;
|
||||
final int maxTextLength;
|
||||
|
||||
GeneratedTextColumn(String name, bool nullable,
|
||||
{this.minTextLength, this.maxTextLength})
|
||||
: super(name, nullable);
|
||||
|
||||
@override
|
||||
Expression<BoolType> like(String regex) =>
|
||||
|
@ -62,6 +68,22 @@ class GeneratedTextColumn extends GeneratedColumn<String, StringType>
|
|||
|
||||
@override
|
||||
final String typeName = 'VARCHAR';
|
||||
|
||||
@override
|
||||
bool isAcceptableValue(String value, bool duringInsert) {
|
||||
final nullOk = !duringInsert || $nullable;
|
||||
if (value == null) return nullOk;
|
||||
|
||||
final length = value.length;
|
||||
if (minTextLength != null && minTextLength > length)
|
||||
return false;
|
||||
if (maxTextLength != null && maxTextLength < length)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class GeneratedBoolColumn extends GeneratedColumn<bool, BoolType>
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import 'package:built_value/built_value.dart';
|
||||
import 'package:sally_generator/src/sqlite_keywords.dart' show isSqliteKeyword;
|
||||
|
||||
part 'specified_column.g.dart';
|
||||
|
||||
enum ColumnType { integer, text, boolean }
|
||||
|
||||
/// Name of a column. Contains additional info on whether the name was chosen
|
||||
/// implicitly (based on the dart getter name) or explicitly (via an named())
|
||||
/// call in the column builder dsl.
|
||||
abstract class ColumnName implements Built<ColumnName, ColumnNameBuilder> {
|
||||
/// A column name is implicit if it has been looked up with the associated
|
||||
/// field name in the table class. It's explicit if `.named()` was called in
|
||||
|
@ -14,6 +18,14 @@ abstract class ColumnName implements Built<ColumnName, ColumnNameBuilder> {
|
|||
|
||||
ColumnName._();
|
||||
|
||||
ColumnName escapeIfSqlKeyword() {
|
||||
if (isSqliteKeyword(name)) {
|
||||
return rebuild((b) => b.name = '`$name`'); // wrap name in backticks
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
factory ColumnName([updates(ColumnNameBuilder b)]) = _$ColumnName;
|
||||
|
||||
factory ColumnName.implicitly(String name) => ColumnName((b) => b
|
||||
|
@ -25,11 +37,18 @@ abstract class ColumnName implements Built<ColumnName, ColumnNameBuilder> {
|
|||
..name = name);
|
||||
}
|
||||
|
||||
/// A column, as specified by a getter in a table.
|
||||
class SpecifiedColumn {
|
||||
/// The getter name of this column in the table class. It will also be used
|
||||
/// as getter name in the TableInfo class (as it needs to override the field)
|
||||
/// and in the generated data class that will be generated for each table.
|
||||
final String dartGetterName;
|
||||
/// The sql type of this column
|
||||
final ColumnType type;
|
||||
/// The name of this column, as chosen by the user
|
||||
final ColumnName name;
|
||||
|
||||
/// Whether this column has auto increment.
|
||||
bool get hasAI => features.any((f) => f is AutoIncrement);
|
||||
|
||||
/// Whether this column has been declared as the primary key via the
|
||||
|
@ -38,24 +57,34 @@ class SpecifiedColumn {
|
|||
final bool declaredAsPrimaryKey;
|
||||
final List<ColumnFeature> features;
|
||||
|
||||
/// The dart type that matches this column. For instance, if a table has
|
||||
/// declared an `IntColumn`, the matching dart type name would be [int].
|
||||
String get dartTypeName => {
|
||||
ColumnType.boolean: 'bool',
|
||||
ColumnType.text: 'String',
|
||||
ColumnType.integer: 'int'
|
||||
}[type];
|
||||
|
||||
/// The column type from the dsl library. For instance, if a table has
|
||||
/// declared an `IntColumn`, the matching dsl column name would also be an
|
||||
/// `IntColumn`.
|
||||
String get dslColumnTypeName => {
|
||||
ColumnType.boolean: 'BoolColumn',
|
||||
ColumnType.text: 'TextColumn',
|
||||
ColumnType.integer: 'IntColumn'
|
||||
}[type];
|
||||
|
||||
/// The `GeneratedColumn` class that implements the [dslColumnTypeName].
|
||||
/// For instance, if a table has declared an `IntColumn`, the matching
|
||||
/// implementation name would be an `GeneratedIntColumn`.
|
||||
String get implColumnTypeName => {
|
||||
ColumnType.boolean: 'GeneratedBoolColumn',
|
||||
ColumnType.text: 'GeneratedTextColumn',
|
||||
ColumnType.integer: 'GeneratedIntColumn'
|
||||
}[type];
|
||||
|
||||
/// The class inside the sally library that represents the same sql type as
|
||||
/// this column.
|
||||
String get sqlTypeName => {
|
||||
ColumnType.boolean: 'BoolType',
|
||||
ColumnType.text: 'StringType',
|
||||
|
|
|
@ -89,7 +89,7 @@ class ColumnParser extends ParserBase {
|
|||
wasDeclaredAsPrimaryKey = true;
|
||||
break;
|
||||
case functionReferences:
|
||||
break; // todo: parsing this is going to suck
|
||||
break;
|
||||
case functionWithLength:
|
||||
final args = remainingExpr.argumentList;
|
||||
final minArg = findNamedArgument(args, 'min');
|
||||
|
@ -121,7 +121,7 @@ class ColumnParser extends ParserBase {
|
|||
return SpecifiedColumn(
|
||||
type: _startMethodToColumnType(foundStartMethod),
|
||||
dartGetterName: getter.name.name,
|
||||
name: name,
|
||||
name: name.escapeIfSqlKeyword(),
|
||||
declaredAsPrimaryKey: wasDeclaredAsPrimaryKey,
|
||||
features: foundFeatures);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:sally_generator/src/errors.dart';
|
|||
import 'package:sally_generator/src/model/specified_column.dart';
|
||||
import 'package:sally_generator/src/model/specified_table.dart';
|
||||
import 'package:sally_generator/src/parser/parser.dart';
|
||||
import 'package:sally_generator/src/sqlite_keywords.dart';
|
||||
import 'package:sally_generator/src/utils/names.dart';
|
||||
import 'package:sally_generator/src/utils/type_utils.dart';
|
||||
import 'package:sally_generator/src/sally_generator.dart'; // ignore: implementation_imports
|
||||
|
@ -18,7 +19,7 @@ class TableParser extends ParserBase {
|
|||
return SpecifiedTable(
|
||||
fromClass: element,
|
||||
columns: _parseColumns(element),
|
||||
sqlName: sqlName,
|
||||
sqlName: escapeIfNeeded(sqlName),
|
||||
dartTypeName: _readDartTypeName(element));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
// https://www.sqlite.org/lang_keywords.html
|
||||
const sqliteKeywords = [
|
||||
'ABORT',
|
||||
'ACTION',
|
||||
'ADD',
|
||||
'AFTER',
|
||||
'ALL',
|
||||
'ALTER',
|
||||
'ANALYZE',
|
||||
'AND',
|
||||
'AS',
|
||||
'ASC',
|
||||
'ATTACH',
|
||||
'AUTOINCREMENT',
|
||||
'BEFORE',
|
||||
'BEGIN',
|
||||
'BETWEEN',
|
||||
'BY',
|
||||
'CASCADE',
|
||||
'CASE',
|
||||
'CAST',
|
||||
'CHECK',
|
||||
'COLLATE',
|
||||
'COLUMN',
|
||||
'COMMIT',
|
||||
'CONFLICT',
|
||||
'CONSTRAINT',
|
||||
'CREATE',
|
||||
'CROSS',
|
||||
'CURRENT',
|
||||
'CURRENT_DATE',
|
||||
'CURRENT_TIME',
|
||||
'CURRENT_TIMESTAMP',
|
||||
'DATABASE',
|
||||
'DEFAULT',
|
||||
'DEFERRABLE',
|
||||
'DEFERRED',
|
||||
'DELETE',
|
||||
'DESC',
|
||||
'DETACH',
|
||||
'DISTINCT',
|
||||
'DO',
|
||||
'DROP',
|
||||
'EACH',
|
||||
'ELSE',
|
||||
'END',
|
||||
'ESCAPE',
|
||||
'EXCEPT',
|
||||
'EXCLUSIVE',
|
||||
'EXISTS',
|
||||
'EXPLAIN',
|
||||
'FAIL',
|
||||
'FILTER',
|
||||
'FOLLOWING',
|
||||
'FOR',
|
||||
'FOREIGN',
|
||||
'FROM',
|
||||
'FULL',
|
||||
'GLOB',
|
||||
'GROUP',
|
||||
'HAVING',
|
||||
'IF',
|
||||
'IGNORE',
|
||||
'IMMEDIATE',
|
||||
'IN',
|
||||
'INDEX',
|
||||
'INDEXED',
|
||||
'INITIALLY',
|
||||
'INNER',
|
||||
'INSERT',
|
||||
'INSTEAD',
|
||||
'INTERSECT',
|
||||
'INTO',
|
||||
'IS',
|
||||
'ISNULL',
|
||||
'JOIN',
|
||||
'KEY',
|
||||
'LEFT',
|
||||
'LIKE',
|
||||
'LIMIT',
|
||||
'MATCH',
|
||||
'NATURAL',
|
||||
'NO',
|
||||
'NOT',
|
||||
'NOTHING',
|
||||
'NOTNULL',
|
||||
'NULL',
|
||||
'OF',
|
||||
'OFFSET',
|
||||
'ON',
|
||||
'OR',
|
||||
'ORDER',
|
||||
'OUTER',
|
||||
'OVER',
|
||||
'PARTITION',
|
||||
'PLAN',
|
||||
'PRAGMA',
|
||||
'PRECEDING',
|
||||
'PRIMARY',
|
||||
'QUERY',
|
||||
'RAISE',
|
||||
'RANGE',
|
||||
'RECURSIVE',
|
||||
'REFERENCES',
|
||||
'REGEXP',
|
||||
'REINDEX',
|
||||
'RELEASE',
|
||||
'RENAME',
|
||||
'REPLACE',
|
||||
'RESTRICT',
|
||||
'RIGHT',
|
||||
'ROLLBACK',
|
||||
'ROW',
|
||||
'ROWS',
|
||||
'SAVEPOINT',
|
||||
'SELECT',
|
||||
'SET',
|
||||
'TABLE',
|
||||
'TEMP',
|
||||
'TEMPORARY',
|
||||
'THEN',
|
||||
'TO',
|
||||
'TRANSACTION',
|
||||
'TRIGGER',
|
||||
'UNBOUNDED',
|
||||
'UNION',
|
||||
'UNIQUE',
|
||||
'UPDATE',
|
||||
'USING',
|
||||
'VACUUM',
|
||||
'VALUES',
|
||||
'VIEW',
|
||||
'VIRTUAL',
|
||||
'WHEN',
|
||||
'WHERE',
|
||||
'WINDOW',
|
||||
'WITH',
|
||||
'WITHOUT'
|
||||
];
|
||||
|
||||
bool isSqliteKeyword(String s) => sqliteKeywords.contains(s.toUpperCase());
|
||||
|
||||
String escapeIfNeeded(String s) {
|
||||
if (isSqliteKeyword(s))
|
||||
return '`$s`';
|
||||
return s;
|
||||
}
|
Loading…
Reference in New Issue