Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Views

Learn how to create views that can be queried.

## Overview

[Views](https://www.sqlite.org/lang_createview.html) are pre-packaged select statements that can
be queried like a table. StructuredQueries comes with tools to create _temporary_ views in a
type-safe and schema-safe fashion.

## Topics

### Creating temporary views

- ``StructuredQueriesCore/Table/createTemporaryView(ifNotExists:as:)``

### Views

- ``TemporaryView``
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ custom database functions, and more.
- <doc:BuiltinFunctions>
- <doc:CustomFunctions>
- <doc:Triggers>
- <doc:Views>
- <doc:FullTextSearch>

### Query representations
Expand Down
66 changes: 66 additions & 0 deletions Sources/StructuredQueriesSQLiteCore/Views.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
extension Table where Self: _Selection {
/// A `CREATE TEMPORARY VIEW` statement.
///
/// See <doc:Views> for more information.
///
/// - Parameters:
/// - ifNotExists: Adds an `IF NOT EXISTS` clause to the `CREATE VIEW` statement.
/// - select: A statement describing the contents of the view.
/// - Returns: A temporary trigger.
public static func createTemporaryView<Selection: SelectStatement>(
ifNotExists: Bool = false,
as select: Selection
) -> TemporaryView<Self, Selection>
where Selection.QueryValue == Columns.QueryValue {
TemporaryView(ifNotExists: ifNotExists, select: select)
}
}

/// A `CREATE TEMPORARY VIEW` statement.
///
/// This type of statement is returned from ``Table/createTemporaryView(ifNotExists:as:)``.
///
/// To learn more, see <doc:Views>.
public struct TemporaryView<View: Table & _Selection, Selection: SelectStatement>: Statement
where Selection.QueryValue == View {
public typealias QueryValue = ()
public typealias From = Never

fileprivate let ifNotExists: Bool
fileprivate let select: Selection

/// Returns a `DROP VIEW` statement for this trigger.
///
/// - Parameter ifExists: Adds an `IF EXISTS` condition to the `DROP VIEW`.
/// - Returns: A `DROP VIEW` statement for this trigger.
public func drop(ifExists: Bool = false) -> some Statement<()> {
var query: QueryFragment = "DROP VIEW"
if ifExists {
query.append(" IF EXISTS")
}
query.append(" ")
if let schemaName = View.schemaName {
query.append("\(quote: schemaName).")
}
query.append(View.tableFragment)
return SQLQueryExpression(query)
}

public var query: QueryFragment {
var query: QueryFragment = "CREATE TEMPORARY VIEW"
if ifNotExists {
query.append(" IF NOT EXISTS")
}
query.append(.newlineOrSpace)
if let schemaName = View.schemaName {
query.append("\(quote: schemaName).")
}
query.append(View.tableFragment)
let columnNames: [QueryFragment] = View.TableColumns.allColumns
.map { "\(quote: $0.name)" }
query.append("\(.newlineOrSpace)(\(columnNames.joined(separator: ", ")))")
query.append("\(.newlineOrSpace)AS")
query.append("\(.newlineOrSpace)\(select)")
return query
}
}
75 changes: 75 additions & 0 deletions Tests/StructuredQueriesTests/ViewsTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import Dependencies
import Foundation
import InlineSnapshotTesting
import StructuredQueries
import Testing
import _StructuredQueriesSQLite

extension SnapshotTests {
@Suite struct ViewsTests {
@Test func basics() {
let query = CompletedReminder.createTemporaryView(
as: Reminder
.where(\.isCompleted)
.select { CompletedReminder.Columns(reminderID: $0.id, title: $0.title) }
)
assertQuery(
query
) {
"""
CREATE TEMPORARY VIEW
"completedReminders"
("reminderID", "title")
AS
SELECT "reminders"."id" AS "reminderID", "reminders"."title" AS "title"
FROM "reminders"
WHERE "reminders"."isCompleted"
"""
} results: {
"""

"""
}
assertQuery(
CompletedReminder.limit(2)
) {
"""
SELECT "completedReminders"."reminderID", "completedReminders"."title"
FROM "completedReminders"
LIMIT 2
"""
} results: {
"""
┌────────────────────────┐
│ CompletedReminder( │
│ reminderID: 4, │
│ title: "Take a walk" │
│ ) │
├────────────────────────┤
│ CompletedReminder( │
│ reminderID: 7, │
│ title: "Get laundry" │
│ ) │
└────────────────────────┘
"""
}
assertQuery(
query.drop()
) {
"""
DROP VIEW "completedReminders"
"""
}
}
}
}

@Table @Selection
private struct CompletedReminder {
let reminderID: Reminder.ID
let title: String
}

extension Table where Self: _Selection {
static func foo() {}
}