Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SQLite 3.44.0, FILTER and ORDER BY clauses in aggregate functions #1452

Merged
merged 5 commits into from
Nov 2, 2023
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
2 changes: 1 addition & 1 deletion Documentation/CustomSQLiteBuilds.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Custom SQLite Builds

By default, GRDB uses the version of SQLite that ships with the target operating system.

**You can build GRDB with a custom build of [SQLite 3.42.0](https://www.sqlite.org/changes.html).**
**You can build GRDB with a custom build of [SQLite 3.44.0](https://www.sqlite.org/changes.html).**

A custom SQLite build can activate extra SQLite features, and extra GRDB features as well, such as support for the [FTS5 full-text search engine](../../../#full-text-search), and [SQLite Pre-Update Hooks](https://swiftpackageindex.com/groue/grdb.swift/documentation/grdb/transactionobserver).

Expand Down
33 changes: 18 additions & 15 deletions GRDB/Core/DatabaseFunction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,6 @@ public final class DatabaseFunction: Hashable {
private let kind: Kind
private var eTextRep: CInt { (SQLITE_UTF8 | (isPure ? SQLITE_DETERMINISTIC : 0)) }

var functionFlags: SQLFunctionFlags {
var flags = SQLFunctionFlags(isPure: isPure)

switch kind {
case .function:
break
case .aggregate:
flags.isAggregate = true
}

return flags
}

/// Creates an SQL function.
///
/// For example:
Expand Down Expand Up @@ -157,6 +144,7 @@ public final class DatabaseFunction: Hashable {
self.kind = .aggregate { Aggregate() }
}

// TODO: GRDB7 -> expose ORDER BY and FILTER when we have distinct types for simple functions and aggregates.
/// Returns an SQL expression that applies the function.
///
/// You can use a `DatabaseFunction` as a regular Swift function. It returns
Expand All @@ -183,9 +171,24 @@ public final class DatabaseFunction: Hashable {
/// }
/// ```
public func callAsFunction(_ arguments: any SQLExpressible...) -> SQLExpression {
.function(name, arguments.map(\.sqlExpression), flags: functionFlags)
switch kind {
case .function:
return .simpleFunction(
name,
arguments.map(\.sqlExpression),
isPure: isPure,
isJSONValue: false)
case .aggregate:
return .aggregateFunction(
name,
arguments.map(\.sqlExpression),
isDistinct: false,
ordering: nil,
filter: nil,
isJSONValue: false)
}
}

/// Calls sqlite3_create_function_v2
/// See <https://sqlite.org/c3ref/create_function.html>
func install(in db: Database) {
Expand Down
4 changes: 2 additions & 2 deletions GRDB/Documentation.docc/JSON.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ The `->` and `->>` SQL operators are available on the ``SQLJSONExpressible`` pro
- ``Database/jsonArray(_:)-469db``
- ``Database/jsonObject(_:)``
- ``Database/jsonQuote(_:)``
- ``Database/jsonGroupArray(_:)``
- ``Database/jsonGroupObject(key:value:)``
- ``Database/jsonGroupArray(_:filter:)``
- ``Database/jsonGroupObject(key:value:filter:)``

### Modify JSON values at the SQL level

Expand Down
101 changes: 93 additions & 8 deletions GRDB/JSON/SQLJSONFunctions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -373,16 +373,61 @@ extension Database {

/// The `JSON_GROUP_ARRAY` SQL function.
///
/// For example:
///
/// ```swift
/// // SELECT JSON_GROUP_ARRAY(name) FROM player
/// Player.select(Database.jsonGroupArray(Column("name")))
///
/// // SELECT JSON_GROUP_ARRAY(name) FILTER (WHERE score > 0) FROM player
/// Player.select(Database.jsonGroupArray(Column("name"), filter: Column("score") > 0))
///
/// // SELECT JSON_GROUP_ARRAY(name ORDER BY name) FROM player
/// Player.select(Database.jsonGroupArray(Column("name"), orderBy: Column("name")))
/// ```
///
/// Related SQLite documentation: <https://www.sqlite.org/json1.html#jgrouparray>
public static func jsonGroupArray(_ value: some SQLExpressible) -> SQLExpression {
.function("JSON_GROUP_ARRAY", [value.sqlExpression.jsonBuilderExpression])
public static func jsonGroupArray(
_ value: some SQLExpressible,
orderBy ordering: (any SQLOrderingTerm)? = nil,
filter: (any SQLSpecificExpressible)? = nil)
-> SQLExpression {
.aggregateFunction(
"JSON_GROUP_ARRAY",
[value.sqlExpression.jsonBuilderExpression],
ordering: ordering?.sqlOrdering,
filter: filter?.sqlExpression,
isJSONValue: true)
}

/// The `JSON_GROUP_OBJECT` SQL function.
///
/// For example:
///
/// ```swift
/// // SELECT JSON_GROUP_OBJECT(name, score) FROM player
/// Player.select(Database.jsonGroupObject(
/// key: Column("name"),
/// value: Column("score")))
///
/// // SELECT JSON_GROUP_OBJECT(name, score) FILTER (WHERE score > 0) FROM player
/// Player.select(Database.jsonGroupObject(
/// key: Column("name"),
/// value: Column("score"),
/// filter: Column("score") > 0))
/// ```
///
/// Related SQLite documentation: <https://www.sqlite.org/json1.html#jgrouparray>
public static func jsonGroupObject(key: some SQLExpressible, value: some SQLExpressible) -> SQLExpression {
.function("JSON_GROUP_OBJECT", [key.sqlExpression, value.sqlExpression.jsonBuilderExpression])
public static func jsonGroupObject(
key: some SQLExpressible,
value: some SQLExpressible,
filter: (any SQLSpecificExpressible)? = nil
) -> SQLExpression {
.aggregateFunction(
"JSON_GROUP_OBJECT",
[key.sqlExpression, value.sqlExpression.jsonBuilderExpression],
filter: filter?.sqlExpression,
isJSONValue: true)
}
}
#else
Expand Down Expand Up @@ -779,18 +824,58 @@ extension Database {

/// The `JSON_GROUP_ARRAY` SQL function.
///
/// For example:
///
/// ```swift
/// // SELECT JSON_GROUP_ARRAY(name) FROM player
/// Player.select(Database.jsonGroupArray(Column("name")))
///
/// // SELECT JSON_GROUP_ARRAY(name) FILTER (WHERE score > 0) FROM player
/// Player.select(Database.jsonGroupArray(Column("name"), filter: Column("score") > 0))
/// ```
///
/// Related SQLite documentation: <https://www.sqlite.org/json1.html#jgrouparray>
@available(iOS 16, macOS 10.15, tvOS 17, watchOS 9, *) // SQLite 3.38+ with exceptions for macOS
public static func jsonGroupArray(_ value: some SQLExpressible) -> SQLExpression {
.function("JSON_GROUP_ARRAY", [value.sqlExpression.jsonBuilderExpression])
public static func jsonGroupArray(
_ value: some SQLExpressible,
filter: (any SQLSpecificExpressible)? = nil)
-> SQLExpression {
.aggregateFunction(
"JSON_GROUP_ARRAY",
[value.sqlExpression.jsonBuilderExpression],
filter: filter?.sqlExpression,
isJSONValue: true)
}

/// The `JSON_GROUP_OBJECT` SQL function.
///
/// For example:
///
/// ```swift
/// // SELECT JSON_GROUP_OBJECT(name, score) FROM player
/// Player.select(Database.jsonGroupObject(
/// key: Column("name"),
/// value: Column("score")))
///
/// // SELECT JSON_GROUP_OBJECT(name, score) FILTER (WHERE score > 0) FROM player
/// Player.select(Database.jsonGroupObject(
/// key: Column("name"),
/// value: Column("score"),
/// filter: Column("score") > 0))
/// ```
///
/// Related SQLite documentation: <https://www.sqlite.org/json1.html#jgrouparray>
@available(iOS 16, macOS 10.15, tvOS 17, watchOS 9, *) // SQLite 3.38+ with exceptions for macOS
public static func jsonGroupObject(key: some SQLExpressible, value: some SQLExpressible) -> SQLExpression {
.function("JSON_GROUP_OBJECT", [key.sqlExpression, value.sqlExpression.jsonBuilderExpression])
public static func jsonGroupObject(
key: some SQLExpressible,
value: some SQLExpressible,
filter: (any SQLSpecificExpressible)? = nil
) -> SQLExpression {
.aggregateFunction(
"JSON_GROUP_OBJECT",
[key.sqlExpression, value.sqlExpression.jsonBuilderExpression],
filter: filter?.sqlExpression,
isJSONValue: true)
}
}
#endif
Loading
Loading