Skip to content

Commit

Permalink
sqlparser: Support sqlite 3.48
Browse files Browse the repository at this point in the history
  • Loading branch information
simolus3 committed Jan 21, 2025
1 parent 334f7f8 commit 71850de
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 8 deletions.
1 change: 1 addition & 0 deletions sqlparser/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## 0.41.0

- Replace `ColonNamedVariable` with `NamedVariable` for all named variables.
- Analysis support for SQLite 3.48.

## 0.40.0

Expand Down
1 change: 1 addition & 0 deletions sqlparser/lib/src/analysis/steps/linting_visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ class LintingVisitor extends RecursiveVisitor<void, void> {
'unhex' => SqliteVersion.v3_41,
'timediff' || 'octet_length' => SqliteVersion.v3_43,
'concat' || 'concat_ws' || 'string_agg' => SqliteVersion.v3_44,
'if' => SqliteVersion.v3_48,
_ => null,
};

Expand Down
18 changes: 17 additions & 1 deletion sqlparser/lib/src/analysis/types/resolving_visitor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -707,14 +707,30 @@ class TypeResolver extends RecursiveVisitor<TypeExpectation, void> {
session._addRelation(CopyTypeFrom(e, params.first));
return const ResolveResult.needsContext();
case 'iif':
checkArgumentCount(3);
case 'if':
final allowedParamLengths = [
3,
if (session.context.engineOptions.version >= SqliteVersion.v3_48) 2,
];

if (!allowedParamLengths.contains(params.length)) {
session.context.reportError(AnalysisError(
type: AnalysisErrorType.invalidAmountOfParameters,
message:
'${e.name} expects 3 arguments (2 are allowed since 3.48), got ${params.length}.',
relevantNode: e.parameters,
));
}

if (params.length == 3) {
// IIF(a, b, c) is essentially CASE WHEN a THEN b ELSE c END
final cases = [params[1], params[2]];
session
.._addRelation(CopyEncapsulating(e, cases))
.._addRelation(HaveSameType(cases));
} else if (params.length == 2) {
// Newer variant: IIF(a, b) is CASE WHEN a THEN b ELSE NULL END
session._addRelation(CopyTypeFrom(e, params[1], makeNullable: true));
}

return const ResolveResult.needsContext();
Expand Down
20 changes: 17 additions & 3 deletions sqlparser/lib/src/engine/module/fts5.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class Fts5Extension implements Extension {

@override
void register(SqlEngine engine) {
engine.registerModule(_Fts5Module());
engine.registerModule(_Fts5Module(engine.options.version));
engine.registerModule(_Fts5VocabModule());
engine.registerFunctionHandler(const _Fts5Functions());
}
Expand All @@ -16,7 +16,15 @@ class _Fts5Module extends Module {
static final RegExp _option = RegExp(r'^(.+)\s*=\s*(.+)$');
static final RegExp _cleanTicks = RegExp('[\'"]');

_Fts5Module() : super('fts5');
final SqliteVersion _version;

_Fts5Module(this._version) : super('fts5');

/// Whether fts5 columns should be assumed to be nullable.
///
/// They are nullable, but for backwards compatibility we only enable this
/// after the user opts into analysis with sqlite 3.48.
bool get assumeNullableColumns => _version >= SqliteVersion.v3_48;

@override
Table parseTable(CreateVirtualTableStatement stmt) {
Expand Down Expand Up @@ -49,7 +57,13 @@ class _Fts5Module extends Module {
contentRowId: (contentTable != null) ? contentRowId : null,
columns: [
for (var arg in columnNames)
TableColumn(arg, const ResolvedType(type: BasicType.text)),
TableColumn(
arg,
ResolvedType(
type: BasicType.text,
nullable: assumeNullableColumns,
),
),
],
definition: stmt,
);
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.47.0` added `if` as an alternative spelling for `iif` and
/// introduces a two-argument version of `iif`.
static const SqliteVersion v3_48 = SqliteVersion.v3(46);

/// Version `3.46.0` added `json_pretty` and allows underscore characters
/// between digits in numeric literals.
static const SqliteVersion v3_46 = SqliteVersion.v3(46);
Expand Down Expand Up @@ -130,7 +134,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_46;
static const SqliteVersion current = v3_48;

/// The major version of sqlite.
///
Expand Down
12 changes: 10 additions & 2 deletions sqlparser/test/analysis/errors/invalid_parameter_count_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,26 @@ import 'package:test/test.dart';
import 'utils.dart';

void main() {
test('is forbidden on older sqlite versions', () {
test('iif requires three arguments on old sqlite versions', () {
final engine = SqlEngine();
final result = engine.analyze('SELECT iif (0, 1)');

expect(result.errors, [
analysisErrorWith(
lexeme: '0, 1',
type: AnalysisErrorType.invalidAmountOfParameters,
message: 'iif expects 3 arguments, got 2.'),
message:
'iif expects 3 arguments (2 are allowed since 3.48), got 2.'),
]);
});

test('iif supports two arguments starting with sqlite 3.48', () {
final engine = SqlEngine(EngineOptions(version: SqliteVersion.v3_48));
final result = engine.analyze('SELECT iif (0, 1)');

expect(result.errors, isEmpty);
});

test('sum', () {
final engine = SqlEngine();
final result = engine.analyze('SELECT sum(1, 2, 3)');
Expand Down
2 changes: 2 additions & 0 deletions sqlparser/test/analysis/types2/misc_cases_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ const Map<String, ResolvedType?> _types = {
ResolvedType(type: BasicType.text, nullable: false),
"SELECT concat_ws(NULL, 1, 2) = ?":
ResolvedType(type: BasicType.text, nullable: true),
"SELECT \"if\"(1, 'hello') = ?":
ResolvedType(type: BasicType.text, nullable: true),
};

SqlEngine _spawnEngine() {
Expand Down
8 changes: 7 additions & 1 deletion sqlparser/test/engine/module/fts5_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import 'package:test/test.dart';

import '../../analysis/data.dart';

final _fts5Options = EngineOptions(enabledExtensions: const [Fts5Extension()]);
final _fts5Options = EngineOptions(
version: SqliteVersion.current,
enabledExtensions: const [
Fts5Extension(),
],
);

void main() {
group('creating fts5 tables', () {
Expand All @@ -20,6 +25,7 @@ void main() {
final columns = table.resultColumns;
expect(columns, hasLength(1));
expect(columns.single.name, 'bar');
expect(columns.single.type.nullable, isTrue);
expect(
table,
isA<Fts5Table>()
Expand Down

0 comments on commit 71850de

Please sign in to comment.