Skip to content

Commit cb85c77

Browse files
committed
Add Table.none
Acts as a "black hole" query, rendering as an empty SQL string and not executed by the database.
1 parent 2d9f1d9 commit cb85c77

File tree

15 files changed

+174
-19
lines changed

15 files changed

+174
-19
lines changed

Sources/StructuredQueries/Macros.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import StructuredQueriesCore
1010
PartialSelectStatement,
1111
PrimaryKeyedTable,
1212
names: named(From),
13+
// named(all),
1314
named(columns),
1415
named(init(_:)),
1516
named(init(decoder:)),
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
enum Scope {
2+
case unscoped
3+
case `default`
4+
case empty
5+
}

Sources/StructuredQueriesCore/Statements/CompoundSelect.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ private struct CompoundSelect<QueryValue>: PartialSelectStatement {
6666
}
6767

6868
var query: QueryFragment {
69-
"\(lhs)\(.newlineOrSpace)\(`operator`.indented())\(.newlineOrSpace)\(rhs)"
69+
guard !lhs.isEmpty else { return rhs }
70+
guard !rhs.isEmpty else { return lhs }
71+
return "\(lhs)\(.newlineOrSpace)\(`operator`.indented())\(.newlineOrSpace)\(rhs)"
7072
}
7173
}

Sources/StructuredQueriesCore/Statements/Delete.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ extension PrimaryKeyedTable {
3838
public struct Delete<From: Table, Returning> {
3939
var `where`: [QueryFragment] = []
4040
var returning: [QueryFragment] = []
41+
var isEmpty = false
4142

4243
/// Adds a condition to a delete statement.
4344
///
@@ -130,6 +131,16 @@ public struct Delete<From: Table, Returning> {
130131
returning: returning
131132
)
132133
}
134+
135+
public var unscoped: Delete<From, ()> {
136+
From.unscoped.delete()
137+
}
138+
139+
public var none: Self {
140+
var delete = self
141+
delete.isEmpty = true
142+
return delete
143+
}
133144
}
134145

135146
/// A convenience type alias for a non-`RETURNING ``Delete``.
@@ -139,6 +150,7 @@ extension Delete: Statement {
139150
public typealias QueryValue = Returning
140151

141152
public var query: QueryFragment {
153+
guard !isEmpty else { return "" }
142154
var query: QueryFragment = "DELETE FROM "
143155
if let schemaName = From.schemaName {
144156
query.append("\(quote: schemaName).")

Sources/StructuredQueriesCore/Statements/Select.swift

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ extension Table {
297297
}
298298

299299
public struct _SelectClauses: Sendable {
300+
var isEmpty = false
300301
var distinct = false
301302
var columns: [any QueryExpression] = []
302303
var joins: [_JoinClause] = []
@@ -320,6 +321,11 @@ public struct Select<Columns, From: Table, Joins> {
320321
// NB: A parameter pack compiler crash forces us to heap-allocate this storage.
321322
@CopyOnWrite var clauses = _SelectClauses()
322323

324+
fileprivate var isEmpty: Bool {
325+
get { clauses.isEmpty }
326+
set { clauses.isEmpty = newValue }
327+
_modify { yield &clauses.isEmpty }
328+
}
323329
fileprivate var distinct: Bool {
324330
get { clauses.distinct }
325331
set { clauses.distinct = newValue }
@@ -362,6 +368,7 @@ public struct Select<Columns, From: Table, Joins> {
362368
}
363369

364370
fileprivate init(
371+
isEmpty: Bool,
365372
distinct: Bool,
366373
columns: [any QueryExpression],
367374
joins: [_JoinClause],
@@ -371,6 +378,7 @@ public struct Select<Columns, From: Table, Joins> {
371378
order: [QueryFragment],
372379
limit: _LimitClause?
373380
) {
381+
self.isEmpty = isEmpty
374382
self.columns = columns
375383
self.distinct = distinct
376384
self.joins = joins
@@ -387,7 +395,8 @@ public struct Select<Columns, From: Table, Joins> {
387395
}
388396

389397
extension Select {
390-
init(where: [QueryFragment] = []) {
398+
init(isEmpty: Bool = false, where: [QueryFragment] = []) {
399+
self.isEmpty = isEmpty
391400
self.where = `where`
392401
}
393402

@@ -558,6 +567,7 @@ extension Select {
558567
Joins == (repeat each J)
559568
{
560569
Select<(repeat each C1, repeat (each C2).QueryValue), From, (repeat each J)>(
570+
isEmpty: isEmpty,
561571
distinct: distinct,
562572
columns: columns + Array(repeat each selection((From.columns, repeat (each J).columns))),
563573
joins: joins,
@@ -612,6 +622,7 @@ extension Select {
612622
)
613623
)
614624
return Select<(repeat each C1, repeat each C2), From, (repeat each J1, F, repeat each J2)>(
625+
isEmpty: isEmpty || other.isEmpty,
615626
distinct: distinct || other.distinct,
616627
columns: columns + other.columns,
617628
joins: joins + [join] + other.joins,
@@ -651,6 +662,7 @@ extension Select {
651662
)
652663
)
653664
return Select<(repeat each C1, repeat each C2), From, (repeat each J, F)>(
665+
isEmpty: isEmpty || other.isEmpty,
654666
distinct: distinct || other.distinct,
655667
columns: columns + other.columns,
656668
joins: joins + [join] + other.joins,
@@ -686,6 +698,7 @@ extension Select {
686698
)
687699
)
688700
return Select<QueryValue, From, (F, repeat each J)>(
701+
isEmpty: isEmpty || other.isEmpty,
689702
distinct: distinct || other.distinct,
690703
columns: columns + other.columns,
691704
joins: joins + [join] + other.joins,
@@ -738,6 +751,7 @@ extension Select {
738751
From,
739752
(repeat each J1, F._Optionalized, repeat (each J2)._Optionalized)
740753
>(
754+
isEmpty: isEmpty || other.isEmpty,
741755
distinct: distinct || other.distinct,
742756
columns: columns + other.columns,
743757
joins: joins + [join] + other.joins,
@@ -785,6 +799,7 @@ extension Select {
785799
From,
786800
(repeat each J, F._Optionalized)
787801
>(
802+
isEmpty: isEmpty || other.isEmpty,
788803
distinct: distinct || other.distinct,
789804
columns: columns + other.columns,
790805
joins: joins + [join] + other.joins,
@@ -822,6 +837,7 @@ extension Select {
822837
)
823838
)
824839
return Select<QueryValue, From, (F._Optionalized, repeat (each J)._Optionalized)>(
840+
isEmpty: isEmpty || other.isEmpty,
825841
distinct: distinct || other.distinct,
826842
columns: columns + other.columns,
827843
joins: joins + [join] + other.joins,
@@ -874,6 +890,7 @@ extension Select {
874890
From._Optionalized,
875891
(repeat (each J1)._Optionalized, F, repeat each J2)
876892
>(
893+
isEmpty: isEmpty || other.isEmpty,
877894
distinct: distinct || other.distinct,
878895
columns: columns + other.columns,
879896
joins: joins + [join] + other.joins,
@@ -921,6 +938,7 @@ extension Select {
921938
From._Optionalized,
922939
(repeat (each J)._Optionalized, F)
923940
>(
941+
isEmpty: isEmpty || other.isEmpty,
924942
distinct: distinct || other.distinct,
925943
columns: columns + other.columns,
926944
joins: joins + [join] + other.joins,
@@ -958,6 +976,7 @@ extension Select {
958976
)
959977
)
960978
return Select<QueryValue, From._Optionalized, (F, repeat each J)>(
979+
isEmpty: isEmpty || other.isEmpty,
961980
distinct: distinct || other.distinct,
962981
columns: columns + other.columns,
963982
joins: joins + [join] + other.joins,
@@ -1010,6 +1029,7 @@ extension Select {
10101029
From._Optionalized,
10111030
(repeat (each J1)._Optionalized, F._Optionalized, repeat (each J2)._Optionalized)
10121031
>(
1032+
isEmpty: isEmpty || other.isEmpty,
10131033
distinct: distinct || other.distinct,
10141034
columns: columns + other.columns,
10151035
joins: joins + [join] + other.joins,
@@ -1057,6 +1077,7 @@ extension Select {
10571077
From._Optionalized,
10581078
(repeat (each J)._Optionalized, F._Optionalized)
10591079
>(
1080+
isEmpty: isEmpty || other.isEmpty,
10601081
distinct: distinct || other.distinct,
10611082
columns: columns + other.columns,
10621083
joins: joins + [join] + other.joins,
@@ -1094,6 +1115,7 @@ extension Select {
10941115
)
10951116
)
10961117
return Select<QueryValue, From._Optionalized, (F._Optionalized, repeat (each J)._Optionalized)>(
1118+
isEmpty: isEmpty || other.isEmpty,
10971119
distinct: distinct || other.distinct,
10981120
columns: columns + other.columns,
10991121
joins: joins + [join] + other.joins,
@@ -1348,6 +1370,7 @@ extension Select {
13481370
SQLQueryExpression(iterator.next()!.queryFragment)
13491371
}
13501372
return Select<(repeat (each C2).QueryValue), From, Joins>(
1373+
isEmpty: isEmpty,
13511374
distinct: distinct,
13521375
columns: Array(repeat each transform(repeat { _ in next() }((each C1).self))),
13531376
joins: joins,
@@ -1358,6 +1381,20 @@ extension Select {
13581381
limit: limit
13591382
)
13601383
}
1384+
1385+
public var unscoped: Where<From> {
1386+
From.unscoped
1387+
}
1388+
1389+
public var all: Self {
1390+
self
1391+
}
1392+
1393+
public var none: Self {
1394+
var select = self
1395+
select.isEmpty = true
1396+
return select
1397+
}
13611398
}
13621399

13631400
/// Combines two select statements of the same table type together.
@@ -1387,6 +1424,7 @@ public func + <
13871424
return Select<
13881425
(repeat each C1, repeat each C2), From, (repeat each J1, repeat each J2)
13891426
>(
1427+
isEmpty: lhs.isEmpty || rhs.isEmpty,
13901428
distinct: lhs.distinct || rhs.distinct,
13911429
columns: lhs.columns + rhs.columns,
13921430
joins: lhs.joins + rhs.joins,
@@ -1406,6 +1444,7 @@ extension Select: SelectStatement {
14061444
}
14071445

14081446
public var query: QueryFragment {
1447+
guard !isEmpty else { return "" }
14091448
var query: QueryFragment = "SELECT"
14101449
let columns =
14111450
columns.isEmpty

Sources/StructuredQueriesCore/Statements/Update.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ public struct Update<From: Table, Returning> {
9595
var updates: Updates<From>
9696
var `where`: [QueryFragment] = []
9797
var returning: [QueryFragment] = []
98+
var isEmpty = false
9899

99100
/// Adds a condition to an update statement.
100101
///
@@ -186,6 +187,16 @@ public struct Update<From: Table, Returning> {
186187
returning: returning
187188
)
188189
}
190+
191+
public var unscoped: Delete<From, ()> {
192+
From.unscoped.delete()
193+
}
194+
195+
public var none: Self {
196+
var delete = self
197+
delete.isEmpty = true
198+
return delete
199+
}
189200
}
190201

191202
/// A convenience type alias for a non-`RETURNING ``Update``.
@@ -195,7 +206,7 @@ extension Update: Statement {
195206
public typealias QueryValue = Returning
196207

197208
public var query: QueryFragment {
198-
guard !updates.isEmpty
209+
guard !isEmpty, !updates.isEmpty
199210
else { return "" }
200211

201212
var query: QueryFragment = "UPDATE "

Sources/StructuredQueriesCore/Statements/Where.swift

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public struct Where<From: Table> {
6464
}
6565

6666
var predicates: [QueryFragment] = []
67-
var unscoped = false
67+
var scope = Scope.default
6868

6969
#if compiler(>=6.1)
7070
public static subscript(dynamicMember keyPath: KeyPath<From.Type, Self>) -> Self {
@@ -94,12 +94,20 @@ extension Where: SelectStatement {
9494
public typealias QueryValue = ()
9595

9696
public func asSelect() -> SelectOf<From> {
97-
(unscoped ? Select() : Select(clauses: From.all._selectClauses))
98-
.and(self)
97+
let select: SelectOf<From>
98+
switch scope {
99+
case .default:
100+
select = Select(clauses: From.all._selectClauses)
101+
case .empty:
102+
select = Select(isEmpty: true, where: predicates)
103+
case .unscoped:
104+
select = Select()
105+
}
106+
return select.and(self)
99107
}
100108

101109
public var _selectClauses: _SelectClauses {
102-
_SelectClauses(where: predicates)
110+
_SelectClauses(isEmpty: scope == .empty, where: predicates)
103111
}
104112

105113
/// A select statement for a column of the filtered table.
@@ -470,7 +478,10 @@ extension Where: SelectStatement {
470478

471479
/// A delete statement for the filtered table.
472480
public func delete() -> DeleteOf<From> {
473-
Delete(where: unscoped ? predicates : From.all._selectClauses.where + predicates)
481+
Delete(
482+
where: scope == .unscoped ? predicates : From.all._selectClauses.where + predicates,
483+
isEmpty: scope == .empty
484+
)
474485
}
475486

476487
/// An update statement for the filtered table.
@@ -486,7 +497,8 @@ extension Where: SelectStatement {
486497
Update(
487498
conflictResolution: conflictResolution,
488499
updates: Updates(updates),
489-
where: unscoped ? predicates : From.all._selectClauses.where + predicates
500+
where: scope == .unscoped ? predicates : From.all._selectClauses.where + predicates,
501+
isEmpty: scope == .empty
490502
)
491503
}
492504

Sources/StructuredQueriesCore/Table.swift

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,6 @@ public protocol Table: QueryRepresentable where TableColumns.QueryValue == Self
4949
static var all: DefaultScope { get }
5050
}
5151

52-
extension Table where DefaultScope == Where<Self> {
53-
public static var all: DefaultScope {
54-
Where()
55-
}
56-
}
57-
5852
extension Table {
5953
/// A select statement on the table with no constraints.
6054
///
@@ -86,7 +80,11 @@ extension Table {
8680
/// // SELECT "reminders"."id" FROM "reminders"
8781
/// ```
8882
public static var unscoped: Where<Self> {
89-
Where(unscoped: true)
83+
Where(scope: .unscoped)
84+
}
85+
86+
public static var none: Where<Self> {
87+
Where(scope: .empty)
9088
}
9189

9290
public static var tableAlias: String? {
@@ -112,3 +110,9 @@ extension Table {
112110
columns[keyPath: keyPath]
113111
}
114112
}
113+
114+
extension Table where DefaultScope == Where<Self> {
115+
public static var all: DefaultScope {
116+
Where()
117+
}
118+
}

0 commit comments

Comments
 (0)