Skip to content

Commit c693e93

Browse files
authored
Add Table.none (pointfreeco#119)
* Add `Table.none` Acts as a "black hole" query, rendering as an empty SQL string and not executed by the database. * wip * wip
1 parent 8198f45 commit c693e93

File tree

17 files changed

+254
-22
lines changed

17 files changed

+254
-22
lines changed

Sources/StructuredQueriesCore/Documentation.docc/Extensions/Select.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222

2323
### Transforming queries
2424

25+
- ``unscoped``
26+
- ``all``
27+
- ``none``
2528
- ``map(_:)``
2629
- ``subscript(dynamicMember:)``
2730
- ``StructuredQueriesCore/+(_:_:)``
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/CommonTableExpression.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,28 @@ public struct With<QueryValue>: Statement {
2929
}
3030

3131
public var query: QueryFragment {
32+
guard !statement.isEmpty else { return "" }
33+
let cteFragments = ctes.compactMap(\.queryFragment.presence)
34+
guard !cteFragments.isEmpty else { return "" }
3235
var query: QueryFragment = "WITH "
3336
query.append(
34-
"\(ctes.map(\.queryFragment).joined(separator: ", "))\(.newlineOrSpace)\(statement)"
37+
"\(cteFragments.joined(separator: ", "))\(.newlineOrSpace)\(statement)"
3538
)
3639
return query
3740
}
3841
}
3942

43+
extension QueryFragment {
44+
fileprivate var presence: Self? { isEmpty ? nil : self }
45+
}
46+
4047
public struct CommonTableExpressionClause: QueryExpression {
4148
public typealias QueryValue = ()
4249
let tableName: QueryFragment
4350
let select: QueryFragment
4451
public var queryFragment: QueryFragment {
45-
"\(tableName) AS (\(.newline)\(select.indented())\(.newline))"
52+
guard !select.isEmpty else { return "" }
53+
return "\(tableName) AS (\(.newline)\(select.indented())\(.newline))"
4654
}
4755
}
4856

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: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ extension PrimaryKeyedTable {
2323
/// - Parameter row: A row to delete.
2424
/// - Returns: A delete statement.
2525
public static func delete(_ row: Self) -> DeleteOf<Self> {
26-
Delete()
26+
delete()
2727
.where {
2828
$0.primaryKey.eq(TableColumns.PrimaryKey(queryOutput: row[keyPath: $0.primaryKey.keyPath]))
2929
}
@@ -36,6 +36,7 @@ extension PrimaryKeyedTable {
3636
///
3737
/// To learn more, see <doc:DeleteStatements>.
3838
public struct Delete<From: Table, Returning> {
39+
var isEmpty: Bool
3940
var `where`: [QueryFragment] = []
4041
var returning: [QueryFragment] = []
4142

@@ -107,6 +108,7 @@ public struct Delete<From: Table, Returning> {
107108
returning.append("\(quote: resultColumn.name)")
108109
}
109110
return Delete<From, (repeat each QueryValue)>(
111+
isEmpty: isEmpty,
110112
where: `where`,
111113
returning: Array(repeat each selection(From.columns))
112114
)
@@ -126,6 +128,7 @@ public struct Delete<From: Table, Returning> {
126128
returning.append("\(quote: resultColumn.name)")
127129
}
128130
return Delete<From, From>(
131+
isEmpty: isEmpty,
129132
where: `where`,
130133
returning: returning
131134
)
@@ -139,6 +142,7 @@ extension Delete: Statement {
139142
public typealias QueryValue = Returning
140143

141144
public var query: QueryFragment {
145+
guard !isEmpty else { return "" }
142146
var query: QueryFragment = "DELETE FROM "
143147
if let schemaName = From.schemaName {
144148
query.append("\(quote: schemaName).")

Sources/StructuredQueriesCore/Statements/Select.swift

Lines changed: 43 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,23 @@ extension Select {
13581381
limit: limit
13591382
)
13601383
}
1384+
1385+
/// Returns a fully unscoped version of this select statement.
1386+
public var unscoped: Where<From> {
1387+
From.unscoped
1388+
}
1389+
1390+
/// Returns this select statement unchanged.
1391+
public var all: Self {
1392+
self
1393+
}
1394+
1395+
/// Returns an empty select statement.
1396+
public var none: Self {
1397+
var select = self
1398+
select.isEmpty = true
1399+
return select
1400+
}
13611401
}
13621402

13631403
/// Combines two select statements of the same table type together.
@@ -1387,6 +1427,7 @@ public func + <
13871427
return Select<
13881428
(repeat each C1, repeat each C2), From, (repeat each J1, repeat each J2)
13891429
>(
1430+
isEmpty: lhs.isEmpty || rhs.isEmpty,
13901431
distinct: lhs.distinct || rhs.distinct,
13911432
columns: lhs.columns + rhs.columns,
13921433
joins: lhs.joins + rhs.joins,
@@ -1406,6 +1447,7 @@ extension Select: SelectStatement {
14061447
}
14071448

14081449
public var query: QueryFragment {
1450+
guard !isEmpty else { return "" }
14091451
var query: QueryFragment = "SELECT"
14101452
let columns =
14111453
columns.isEmpty

Sources/StructuredQueriesCore/Statements/Update.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ extension PrimaryKeyedTable {
9191
///
9292
/// To learn more, see <doc:UpdateStatements>.
9393
public struct Update<From: Table, Returning> {
94+
var isEmpty: Bool
9495
var conflictResolution: ConflictResolution?
9596
var updates: Updates<From>
9697
var `where`: [QueryFragment] = []
@@ -159,6 +160,7 @@ public struct Update<From: Table, Returning> {
159160
returning.append("\(quote: resultColumn.name)")
160161
}
161162
return Update<From, (repeat each QueryValue)>(
163+
isEmpty: false,
162164
conflictResolution: conflictResolution,
163165
updates: updates,
164166
where: `where`,
@@ -180,6 +182,7 @@ public struct Update<From: Table, Returning> {
180182
returning.append("\(quote: resultColumn.name)")
181183
}
182184
return Update<From, From>(
185+
isEmpty: isEmpty,
183186
conflictResolution: conflictResolution,
184187
updates: updates,
185188
where: `where`,
@@ -195,7 +198,7 @@ extension Update: Statement {
195198
public typealias QueryValue = Returning
196199

197200
public var query: QueryFragment {
198-
guard !updates.isEmpty
201+
guard !isEmpty, !updates.isEmpty
199202
else { return "" }
200203

201204
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.
@@ -476,7 +484,10 @@ extension Where: SelectStatement {
476484

477485
/// A delete statement for the filtered table.
478486
public func delete() -> DeleteOf<From> {
479-
Delete(where: unscoped ? predicates : From.all._selectClauses.where + predicates)
487+
Delete(
488+
isEmpty: scope == .empty,
489+
where: scope == .unscoped ? predicates : From.all._selectClauses.where + predicates
490+
)
480491
}
481492

482493
/// An update statement for the filtered table.
@@ -490,9 +501,10 @@ extension Where: SelectStatement {
490501
set updates: (inout Updates<From>) -> Void
491502
) -> UpdateOf<From> {
492503
Update(
504+
isEmpty: scope == .empty,
493505
conflictResolution: conflictResolution,
494506
updates: Updates(updates),
495-
where: unscoped ? predicates : From.all._selectClauses.where + predicates
507+
where: scope == .unscoped ? predicates : From.all._selectClauses.where + predicates
496508
)
497509
}
498510

0 commit comments

Comments
 (0)