Skip to content

Commit

Permalink
Support timediff and octet_length
Browse files Browse the repository at this point in the history
  • Loading branch information
simolus3 committed Aug 17, 2023
1 parent d84767b commit fe242e5
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 63 deletions.
4 changes: 2 additions & 2 deletions docs/lib/snippets/modular/drift/example.drift.dart
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ class TodosCompanion extends i0.UpdateCompanion<i1.Todo> {

@override
String toString() {
return (StringBuffer('i1.TodosCompanion(')
return (StringBuffer('TodosCompanion(')
..write('id: $id, ')
..write('title: $title, ')
..write('content: $content, ')
Expand Down Expand Up @@ -426,7 +426,7 @@ class CategoriesCompanion extends i0.UpdateCompanion<i1.Category> {

@override
String toString() {
return (StringBuffer('i1.CategoriesCompanion(')
return (StringBuffer('CategoriesCompanion(')
..write('id: $id, ')
..write('description: $description')
..write(')'))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ class TypeConverterHint extends TypeHint {
TypeConverterHint(this.converter);
}

class _SimpleColumn extends Column with ColumnWithType {
class _SimpleColumn extends Column implements ColumnWithType {
@override
final String name;
@override
Expand Down
2 changes: 2 additions & 0 deletions sqlparser/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

- Add the `sqlite3_schema` table to the builtin tables supported by every
`SqlEngine` instance.
- Support the `timediff` and `octet_length` function which will be released in
sqlite 3.43.0.

## 0.31.0

Expand Down
2 changes: 1 addition & 1 deletion sqlparser/lib/src/analysis/schema/column.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ abstract class Column
}

/// A column that has a statically known resolved type.
abstract class ColumnWithType implements Column {
abstract interface class ColumnWithType implements Column {
/// The type of this column, which is available before any resolution happens
/// (we know it from the schema structure).
ResolvedType? get type;
Expand Down
2 changes: 1 addition & 1 deletion sqlparser/lib/src/analysis/schema/result_set.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ abstract class ResolvesToResultSet with Referencable {
}

/// Something that returns a set of columns when evaluated.
abstract class ResultSet implements ResolvesToResultSet {
abstract mixin class ResultSet implements ResolvesToResultSet {
/// The columns that will be returned when evaluating this query.
List<Column>? get resolvedColumns;

Expand Down
71 changes: 33 additions & 38 deletions sqlparser/lib/src/analysis/steps/linting_visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -258,44 +258,39 @@ class LintingVisitor extends RecursiveVisitor<void, void> {
options.addedFunctions[lowercaseCall]!.reportErrors(e, context);
}

switch (e.name.toLowerCase()) {
case 'format':
case 'unixepoch':
// These were added in sqlite3 version 3.38
if (options.version < SqliteVersion.v3_38) {
context.reportError(
AnalysisError(
type: AnalysisErrorType.notSupportedInDesiredVersion,
message: 'The `${e.name}` function requires sqlite 3.38 or later',
relevantNode: e,
),
);
}
break;
case 'printf':
// `printf` was renamed to `format` in sqlite3 version 3.38
if (options.version >= SqliteVersion.v3_38) {
context.reportError(
AnalysisError(
type: AnalysisErrorType.hint,
message: '`printf` was renamed to `format()`, consider using '
'that function instead.',
relevantNode: e,
),
);
}
break;
case 'unhex':
if (options.version < SqliteVersion.v3_41) {
context.reportError(
AnalysisError(
type: AnalysisErrorType.notSupportedInDesiredVersion,
message: '`unhex` requires sqlite 3.41',
relevantNode: e.nameToken ?? e,
),
);
}
break;
final lowerCaseName = e.name.toLowerCase();
if (lowerCaseName == 'printf' && options.version >= SqliteVersion.v3_38) {
// `printf` was renamed to `format` in sqlite3 version 3.38
if (options.version >= SqliteVersion.v3_38) {
context.reportError(
AnalysisError(
type: AnalysisErrorType.hint,
message: '`printf` was renamed to `format()`, consider using '
'that function instead.',
relevantNode: e,
),
);
}
}

// Warn when newer functions are used in an unsupported sqlite3 version.
final minimumVersion = switch (lowerCaseName) {
'format' || 'unixepoch' => SqliteVersion.v3_38,
'unhex' => SqliteVersion.v3_41,
'timediff' || 'octet_length' => SqliteVersion.v3_43,
_ => null,
};

if (minimumVersion != null && options.version < minimumVersion) {
final versionStr = '${minimumVersion.major}.${minimumVersion.minor}';

context.reportError(
AnalysisError(
type: AnalysisErrorType.notSupportedInDesiredVersion,
message: '`${e.name}` requires sqlite $versionStr or later',
relevantNode: e.nameToken ?? e,
),
);
}

visitChildren(e, arg);
Expand Down
15 changes: 15 additions & 0 deletions sqlparser/lib/src/analysis/types/resolving_visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,7 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
case 'sqlite_compileoption_set':
case 'sqlite_version':
case 'typeof':
case 'timediff':
return _textType;
case 'datetime':
return _textType.copyWith(hint: const IsDateTime(), nullable: true);
Expand All @@ -591,6 +592,7 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
case 'rank':
case 'dense_rank':
case 'ntile':
case 'octet_length':
return _intType;
case 'instr':
case 'length':
Expand Down Expand Up @@ -700,6 +702,19 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
visited.add(param);
}
}
case 'timediff':
for (var i = 0; i < min(2, params.length); i++) {
final param = params[i];
if (param is Expression) {
visit(
param,
const ExactTypeExpectation(ResolvedType(
type: BasicType.text,
hint: IsDateTime(),
)));
visited.add(param);
}
}
break;
}

Expand Down
6 changes: 5 additions & 1 deletion sqlparser/lib/src/engine/options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ class SqliteVersion implements Comparable<SqliteVersion> {
/// can't provide analysis warnings when using recent sqlite3 features.
static const SqliteVersion minimum = SqliteVersion.v3(34);

/// Version `3.43.0` added the built-in `timediff` and `octet_length`
/// functions.
static const SqliteVersion v3_43 = SqliteVersion.v3(43);

/// Version `3.41.0` added the built-in `unhex` function.
static const SqliteVersion v3_41 = SqliteVersion.v3(41);

Expand All @@ -114,7 +118,7 @@ class SqliteVersion implements Comparable<SqliteVersion> {
/// The highest sqlite version supported by this `sqlparser` package.
///
/// Newer features in `sqlite3` may not be recognized by this library.
static const SqliteVersion current = v3_41;
static const SqliteVersion current = v3_43;

/// The major version of sqlite.
///
Expand Down
4 changes: 2 additions & 2 deletions sqlparser/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ repository: https:/simolus3/drift
issue_tracker: https:/simolus3/drift/issues

environment:
sdk: '>=2.17.0 <4.0.0'
sdk: '>=3.0.0 <4.0.0'

dependencies:
meta: ^1.3.0
Expand All @@ -20,4 +20,4 @@ dev_dependencies:
test: ^1.17.4
path: ^1.8.0
ffi: ^2.0.0
sqlite3: ^1.0.0
sqlite3: ^2.0.0
20 changes: 18 additions & 2 deletions sqlparser/test/analysis/errors/unsupported_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,15 @@ void main() {
test('warns about using unixepoch before 3.38', () {
const sql = "SELECT unixepoch('')";

minimumEngine.analyze(sql).expectError("unixepoch('')",
minimumEngine.analyze(sql).expectError('unixepoch',
type: AnalysisErrorType.notSupportedInDesiredVersion);
currentEngine.analyze(sql).expectNoError();
});

test('warns about using format before 3.38', () {
const sql = "SELECT format('', 0, 'foo')";

minimumEngine.analyze(sql).expectError("format('', 0, 'foo')",
minimumEngine.analyze(sql).expectError('format',
type: AnalysisErrorType.notSupportedInDesiredVersion);
currentEngine.analyze(sql).expectNoError();
});
Expand All @@ -114,6 +114,22 @@ void main() {
currentEngine.analyze(sql).expectNoError();
});

test('warns about timediff before 3.43', () {
const sql = "SELECT timediff(?, ?)";

minimumEngine.analyze(sql).expectError('timediff',
type: AnalysisErrorType.notSupportedInDesiredVersion);
currentEngine.analyze(sql).expectNoError();
});

test('warns about octet_length before 3.43', () {
const sql = "SELECT octet_length('abcd')";

minimumEngine.analyze(sql).expectError('octet_length',
type: AnalysisErrorType.notSupportedInDesiredVersion);
currentEngine.analyze(sql).expectNoError();
});

test('warns about `IS DISTINCT FROM`', () {
const sql = 'SELECT id IS DISTINCT FROM content FROM demo;';
const notSql = 'SELECT id IS NOT DISTINCT FROM content FROM demo;';
Expand Down
46 changes: 31 additions & 15 deletions sqlparser/test/analysis/types2/resolver_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -176,21 +176,37 @@ void main() {
expect(escapedType, const ResolvedType(type: BasicType.text));
});

test('handles nth_value', () {
final resolver = obtainResolver("SELECT nth_value('string', ?1) = ?2");
final variables = resolver.session.context.root.allDescendants
.whereType<Variable>()
.iterator;
variables.moveNext();
final firstVar = variables.current;
variables.moveNext();
final secondVar = variables.current;

expect(resolver.session.typeOf(firstVar),
equals(const ResolvedType(type: BasicType.int)));

expect(resolver.session.typeOf(secondVar),
equals(const ResolvedType(type: BasicType.text)));
group('function', () {
test('timediff', () {
final resultType = resolveResultColumn('SELECT timediff(?, ?)');
final argType = resolveFirstVariable('SELECT timediff(?, ?)');

expect(resultType, const ResolvedType(type: BasicType.text));
expect(argType,
const ResolvedType(type: BasicType.text, hint: IsDateTime()));
});

test('octet_length', () {
expect(resolveResultColumn('SELECT octet_length(?)'),
equals(const ResolvedType(type: BasicType.int)));
});

test('nth_value', () {
final resolver = obtainResolver("SELECT nth_value('string', ?1) = ?2");
final variables = resolver.session.context.root.allDescendants
.whereType<Variable>()
.iterator;
variables.moveNext();
final firstVar = variables.current;
variables.moveNext();
final secondVar = variables.current;

expect(resolver.session.typeOf(firstVar),
equals(const ResolvedType(type: BasicType.int)));

expect(resolver.session.typeOf(secondVar),
equals(const ResolvedType(type: BasicType.text)));
});
});

group('case expressions', () {
Expand Down

0 comments on commit fe242e5

Please sign in to comment.