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
|
- Custom primary keys
|
||||||
- Stabilize all end-user APIs and document them extensively
|
- Stabilize all end-user APIs and document them extensively
|
||||||
- Support default values and expressions, auto-increment
|
- Support default values and expressions, auto-increment
|
||||||
- Escape table and column names
|
|
||||||
- Auto-updating streams for select statements
|
- Auto-updating streams for select statements
|
||||||
##### Definitely planned for the future
|
##### Definitely planned for the future
|
||||||
- Allow using DAOs instead of having to put everything in the main database
|
- 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
|
@visibleForOverriding
|
||||||
void writeCustomConstraints(StringBuffer into) {}
|
void writeCustomConstraints(StringBuffer into) {}
|
||||||
|
|
||||||
@visibleForOverriding
|
@visibleForOverriding
|
||||||
String get typeName;
|
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>
|
class GeneratedTextColumn extends GeneratedColumn<String, StringType>
|
||||||
implements TextColumn {
|
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
|
@override
|
||||||
Expression<BoolType> like(String regex) =>
|
Expression<BoolType> like(String regex) =>
|
||||||
|
@ -62,6 +68,22 @@ class GeneratedTextColumn extends GeneratedColumn<String, StringType>
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final String typeName = 'VARCHAR';
|
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>
|
class GeneratedBoolColumn extends GeneratedColumn<bool, BoolType>
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import 'package:built_value/built_value.dart';
|
import 'package:built_value/built_value.dart';
|
||||||
|
import 'package:sally_generator/src/sqlite_keywords.dart' show isSqliteKeyword;
|
||||||
|
|
||||||
part 'specified_column.g.dart';
|
part 'specified_column.g.dart';
|
||||||
|
|
||||||
enum ColumnType { integer, text, boolean }
|
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> {
|
abstract class ColumnName implements Built<ColumnName, ColumnNameBuilder> {
|
||||||
/// A column name is implicit if it has been looked up with the associated
|
/// 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
|
/// 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._();
|
||||||
|
|
||||||
|
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([updates(ColumnNameBuilder b)]) = _$ColumnName;
|
||||||
|
|
||||||
factory ColumnName.implicitly(String name) => ColumnName((b) => b
|
factory ColumnName.implicitly(String name) => ColumnName((b) => b
|
||||||
|
@ -25,11 +37,18 @@ abstract class ColumnName implements Built<ColumnName, ColumnNameBuilder> {
|
||||||
..name = name);
|
..name = name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A column, as specified by a getter in a table.
|
||||||
class SpecifiedColumn {
|
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;
|
final String dartGetterName;
|
||||||
|
/// The sql type of this column
|
||||||
final ColumnType type;
|
final ColumnType type;
|
||||||
|
/// The name of this column, as chosen by the user
|
||||||
final ColumnName name;
|
final ColumnName name;
|
||||||
|
|
||||||
|
/// Whether this column has auto increment.
|
||||||
bool get hasAI => features.any((f) => f is AutoIncrement);
|
bool get hasAI => features.any((f) => f is AutoIncrement);
|
||||||
|
|
||||||
/// Whether this column has been declared as the primary key via the
|
/// Whether this column has been declared as the primary key via the
|
||||||
|
@ -38,24 +57,34 @@ class SpecifiedColumn {
|
||||||
final bool declaredAsPrimaryKey;
|
final bool declaredAsPrimaryKey;
|
||||||
final List<ColumnFeature> features;
|
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 => {
|
String get dartTypeName => {
|
||||||
ColumnType.boolean: 'bool',
|
ColumnType.boolean: 'bool',
|
||||||
ColumnType.text: 'String',
|
ColumnType.text: 'String',
|
||||||
ColumnType.integer: 'int'
|
ColumnType.integer: 'int'
|
||||||
}[type];
|
}[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 => {
|
String get dslColumnTypeName => {
|
||||||
ColumnType.boolean: 'BoolColumn',
|
ColumnType.boolean: 'BoolColumn',
|
||||||
ColumnType.text: 'TextColumn',
|
ColumnType.text: 'TextColumn',
|
||||||
ColumnType.integer: 'IntColumn'
|
ColumnType.integer: 'IntColumn'
|
||||||
}[type];
|
}[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 => {
|
String get implColumnTypeName => {
|
||||||
ColumnType.boolean: 'GeneratedBoolColumn',
|
ColumnType.boolean: 'GeneratedBoolColumn',
|
||||||
ColumnType.text: 'GeneratedTextColumn',
|
ColumnType.text: 'GeneratedTextColumn',
|
||||||
ColumnType.integer: 'GeneratedIntColumn'
|
ColumnType.integer: 'GeneratedIntColumn'
|
||||||
}[type];
|
}[type];
|
||||||
|
|
||||||
|
/// The class inside the sally library that represents the same sql type as
|
||||||
|
/// this column.
|
||||||
String get sqlTypeName => {
|
String get sqlTypeName => {
|
||||||
ColumnType.boolean: 'BoolType',
|
ColumnType.boolean: 'BoolType',
|
||||||
ColumnType.text: 'StringType',
|
ColumnType.text: 'StringType',
|
||||||
|
|
|
@ -89,7 +89,7 @@ class ColumnParser extends ParserBase {
|
||||||
wasDeclaredAsPrimaryKey = true;
|
wasDeclaredAsPrimaryKey = true;
|
||||||
break;
|
break;
|
||||||
case functionReferences:
|
case functionReferences:
|
||||||
break; // todo: parsing this is going to suck
|
break;
|
||||||
case functionWithLength:
|
case functionWithLength:
|
||||||
final args = remainingExpr.argumentList;
|
final args = remainingExpr.argumentList;
|
||||||
final minArg = findNamedArgument(args, 'min');
|
final minArg = findNamedArgument(args, 'min');
|
||||||
|
@ -121,7 +121,7 @@ class ColumnParser extends ParserBase {
|
||||||
return SpecifiedColumn(
|
return SpecifiedColumn(
|
||||||
type: _startMethodToColumnType(foundStartMethod),
|
type: _startMethodToColumnType(foundStartMethod),
|
||||||
dartGetterName: getter.name.name,
|
dartGetterName: getter.name.name,
|
||||||
name: name,
|
name: name.escapeIfSqlKeyword(),
|
||||||
declaredAsPrimaryKey: wasDeclaredAsPrimaryKey,
|
declaredAsPrimaryKey: wasDeclaredAsPrimaryKey,
|
||||||
features: foundFeatures);
|
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_column.dart';
|
||||||
import 'package:sally_generator/src/model/specified_table.dart';
|
import 'package:sally_generator/src/model/specified_table.dart';
|
||||||
import 'package:sally_generator/src/parser/parser.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/names.dart';
|
||||||
import 'package:sally_generator/src/utils/type_utils.dart';
|
import 'package:sally_generator/src/utils/type_utils.dart';
|
||||||
import 'package:sally_generator/src/sally_generator.dart'; // ignore: implementation_imports
|
import 'package:sally_generator/src/sally_generator.dart'; // ignore: implementation_imports
|
||||||
|
@ -18,7 +19,7 @@ class TableParser extends ParserBase {
|
||||||
return SpecifiedTable(
|
return SpecifiedTable(
|
||||||
fromClass: element,
|
fromClass: element,
|
||||||
columns: _parseColumns(element),
|
columns: _parseColumns(element),
|
||||||
sqlName: sqlName,
|
sqlName: escapeIfNeeded(sqlName),
|
||||||
dartTypeName: _readDartTypeName(element));
|
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