diff --git a/sqlparser/CHANGELOG.md b/sqlparser/CHANGELOG.md index a8b23b0d..f7f79428 100644 --- a/sqlparser/CHANGELOG.md +++ b/sqlparser/CHANGELOG.md @@ -1,5 +1,7 @@ ## 0.15.0-dev +- __Breaking__: Change `InsertStatement.upsert` to a list of upsert clauses + - Support multiple upsert clauses - Support `FROM` clauses in `UPDATE` statements - Support `MATERIALIZED`/`NOT MATERIALIZED` hints in common table expressions - Add `BuiltInMathExtension` which corresponds to the `-DSQLITE_ENABLE_MATH_FUNCTIONS` diff --git a/sqlparser/lib/src/analysis/types/resolving_visitor.dart b/sqlparser/lib/src/analysis/types/resolving_visitor.dart index 6f89417d..1e7fde8c 100644 --- a/sqlparser/lib/src/analysis/types/resolving_visitor.dart +++ b/sqlparser/lib/src/analysis/types/resolving_visitor.dart @@ -102,7 +102,7 @@ class TypeResolver extends RecursiveVisitor { }, ); - visitNullable(e.upsert, const NoTypeExpectation()); + visitList(e.upsert, const NoTypeExpectation()); } @override diff --git a/sqlparser/lib/src/ast/ast.dart b/sqlparser/lib/src/ast/ast.dart index ad3f8cf2..60e73551 100644 --- a/sqlparser/lib/src/ast/ast.dart +++ b/sqlparser/lib/src/ast/ast.dart @@ -7,9 +7,9 @@ import 'node.dart'; import 'statements/create_index.dart'; import 'statements/select.dart'; import 'statements/statement.dart'; -import 'statements/update.dart'; import 'visitor.dart'; +export 'clauses/upsert.dart'; export 'node.dart'; export 'statements/block.dart'; export 'statements/create_index.dart'; @@ -27,7 +27,6 @@ export 'visitor.dart'; // todo: Split up this mega-library part 'clauses/limit.dart'; part 'clauses/ordering.dart'; -part 'clauses/upsert.dart'; part 'clauses/with.dart'; part 'common/queryables.dart'; part 'common/renamable.dart'; diff --git a/sqlparser/lib/src/ast/clauses/upsert.dart b/sqlparser/lib/src/ast/clauses/upsert.dart index 272d5288..ac113999 100644 --- a/sqlparser/lib/src/ast/clauses/upsert.dart +++ b/sqlparser/lib/src/ast/clauses/upsert.dart @@ -1,4 +1,6 @@ -part of '../ast.dart'; +import '../ast.dart'; // todo: Remove this import +import '../node.dart'; +import '../statements/create_index.dart' show IndexedColumn; class UpsertClause extends AstNode implements HasWhereClause { final List? onColumns; diff --git a/sqlparser/lib/src/ast/statements/insert.dart b/sqlparser/lib/src/ast/statements/insert.dart index c987efaf..bc43e5d5 100644 --- a/sqlparser/lib/src/ast/statements/insert.dart +++ b/sqlparser/lib/src/ast/statements/insert.dart @@ -1,5 +1,6 @@ import '../../analysis/analysis.dart'; import '../ast.dart'; // todo: Remove this import +import '../clauses/upsert.dart'; import '../node.dart'; import '../visitor.dart'; import 'statement.dart'; @@ -20,7 +21,7 @@ class InsertStatement extends CrudStatement implements HasPrimarySource { TableReference? table; final List targetColumns; InsertSource source; - UpsertClause? upsert; + final List upsert; List? get resolvedTargetColumns { if (targetColumns.isNotEmpty) { @@ -37,7 +38,7 @@ class InsertStatement extends CrudStatement implements HasPrimarySource { required this.table, required this.targetColumns, required this.source, - this.upsert}) + this.upsert = const []}) : super(withClause); @override @@ -58,7 +59,7 @@ class InsertStatement extends CrudStatement implements HasPrimarySource { table!, ...targetColumns, source, - if (upsert != null) upsert! + ...upsert, ]; } diff --git a/sqlparser/lib/src/ast/visitor.dart b/sqlparser/lib/src/ast/visitor.dart index bdd89a98..9acb5bdf 100644 --- a/sqlparser/lib/src/ast/visitor.dart +++ b/sqlparser/lib/src/ast/visitor.dart @@ -584,9 +584,9 @@ extension VisitChildrenExtension on AstVisitor { } /// Visits all [nodes] in sequence. - R? visitList(Iterable nodes, A arg) { + R? visitList(Iterable nodes, A arg) { for (final node in nodes) { - node!.accept(this, arg); + node.accept(this, arg); } return null; } diff --git a/sqlparser/lib/src/reader/parser/parser.dart b/sqlparser/lib/src/reader/parser/parser.dart index fbccc75f..907b6215 100644 --- a/sqlparser/lib/src/reader/parser/parser.dart +++ b/sqlparser/lib/src/reader/parser/parser.dart @@ -1538,7 +1538,10 @@ class Parser { 'Expected clpsing parenthesis after column list'); } final source = _insertSource(); - final upsert = _upsertClauseOrNull(); + final upsert = []; + while (_check(TokenType.on)) { + upsert.add(_upsertClause()); + } return InsertStatement( withClause: withClause, @@ -1572,10 +1575,8 @@ class Parser { } } - UpsertClause? _upsertClauseOrNull() { - if (!_matchOne(TokenType.on)) return null; - - final first = _previous; + UpsertClause _upsertClause() { + final first = _consume(TokenType.on); _consume(TokenType.conflict, 'Expected CONFLICT keyword for upsert clause'); List? indexedColumns; diff --git a/sqlparser/lib/utils/node_to_text.dart b/sqlparser/lib/utils/node_to_text.dart index bed507b2..b7ce33fc 100644 --- a/sqlparser/lib/utils/node_to_text.dart +++ b/sqlparser/lib/utils/node_to_text.dart @@ -715,7 +715,7 @@ class NodeSqlBuilder extends AstVisitor { } visit(e.source, arg); - visitNullable(e.upsert, arg); + _join(e.upsert, ''); } @override diff --git a/sqlparser/test/parser/insert_test.dart b/sqlparser/test/parser/insert_test.dart index 4500aa24..1f858ef4 100644 --- a/sqlparser/test/parser/insert_test.dart +++ b/sqlparser/test/parser/insert_test.dart @@ -64,7 +64,7 @@ void main() { table: TableReference('tbl'), targetColumns: const [], source: DefaultValues(), - upsert: UpsertClause(action: DoNothing()), + upsert: [UpsertClause(action: DoNothing())], ), ); }); @@ -76,16 +76,18 @@ void main() { table: TableReference('tbl'), targetColumns: const [], source: DefaultValues(), - upsert: UpsertClause( - onColumns: [ - IndexedColumn(Reference(columnName: 'foo')), - IndexedColumn( - Reference(columnName: 'bar'), - OrderingMode.descending, - ), - ], - action: DoNothing(), - ), + upsert: [ + UpsertClause( + onColumns: [ + IndexedColumn(Reference(columnName: 'foo')), + IndexedColumn( + Reference(columnName: 'bar'), + OrderingMode.descending, + ), + ], + action: DoNothing(), + ), + ], ), ); }); @@ -97,18 +99,20 @@ void main() { table: TableReference('tbl'), targetColumns: const [], source: DefaultValues(), - upsert: UpsertClause( - onColumns: [ - IndexedColumn(Reference(columnName: 'foo')), - IndexedColumn(Reference(columnName: 'bar')), - ], - where: BinaryExpression( - NumericLiteral(2, token(TokenType.numberLiteral)), - token(TokenType.equal), - Reference(columnName: 'foo'), + upsert: [ + UpsertClause( + onColumns: [ + IndexedColumn(Reference(columnName: 'foo')), + IndexedColumn(Reference(columnName: 'bar')), + ], + where: BinaryExpression( + NumericLiteral(2, token(TokenType.numberLiteral)), + token(TokenType.equal), + Reference(columnName: 'foo'), + ), + action: DoNothing(), ), - action: DoNothing(), - ), + ], ), ); }); @@ -120,16 +124,19 @@ void main() { table: TableReference('tbl'), targetColumns: const [], source: DefaultValues(), - upsert: UpsertClause( - action: DoUpdate( - [ - SetComponent( - column: Reference(columnName: 'foo'), - expression: NumericLiteral(2, token(TokenType.numberLiteral)), - ), - ], + upsert: [ + UpsertClause( + action: DoUpdate( + [ + SetComponent( + column: Reference(columnName: 'foo'), + expression: + NumericLiteral(2, token(TokenType.numberLiteral)), + ), + ], + ), ), - ), + ], ), ); }); @@ -141,19 +148,51 @@ void main() { table: TableReference('tbl'), targetColumns: const [], source: DefaultValues(), - upsert: UpsertClause( - action: DoUpdate( - [ - SetComponent( - column: Reference(columnName: 'foo'), - expression: NumericLiteral(2, token(TokenType.numberLiteral)), + upsert: [ + UpsertClause( + action: DoUpdate( + [ + SetComponent( + column: Reference(columnName: 'foo'), + expression: + NumericLiteral(2, token(TokenType.numberLiteral)), + ), + ], + where: NumberedVariable( + QuestionMarkVariableToken(fakeSpan('?'), null), ), - ], - where: NumberedVariable( - QuestionMarkVariableToken(fakeSpan('?'), null), ), ), - ), + ], + ), + ); + }); + + test('having more than one clause', () { + testStatement( + '$prefix (foo) DO NOTHING ON CONFLICT (bar) DO UPDATE SET x = 2', + InsertStatement( + table: TableReference('tbl'), + targetColumns: const [], + source: DefaultValues(), + upsert: [ + UpsertClause( + onColumns: [IndexedColumn(Reference(columnName: 'foo'))], + action: DoNothing(), + ), + UpsertClause( + onColumns: [IndexedColumn(Reference(columnName: 'bar'))], + action: DoUpdate( + [ + SetComponent( + column: Reference(columnName: 'x'), + expression: + NumericLiteral(2, token(TokenType.numberLiteral)), + ), + ], + ), + ), + ], ), ); }); diff --git a/sqlparser/test/utils/node_to_text_test.dart b/sqlparser/test/utils/node_to_text_test.dart index d226f0ab..d68fffad 100644 --- a/sqlparser/test/utils/node_to_text_test.dart +++ b/sqlparser/test/utils/node_to_text_test.dart @@ -247,6 +247,12 @@ CREATE UNIQUE INDEX my_idx ON t1 (c1, c2, c3) WHERE c1 < c3; testFormat('INSERT INTO foo VALUES (1, 2, 3) ' 'ON CONFLICT DO UPDATE SET a = b, c = d WHERE d < a;'); }); + + test('upsert - multiple clauses', () { + testFormat('INSERT INTO foo VALUES (1, 2, 3) ' + 'ON CONFLICT DO NOTHING ' + 'ON CONFLICT DO UPDATE SET a = b, c = d WHERE d < a;'); + }); }); group('update', () {