Skip to content

Commit

Permalink
Merge pull request #1452 from groue/dev/SQLite-3.44.0
Browse files Browse the repository at this point in the history
SQLite 3.44.0, FILTER and ORDER BY clauses in aggregate functions
  • Loading branch information
groue authored Nov 2, 2023
2 parents f1f8e84 + f0fe5ff commit 753d7db
Show file tree
Hide file tree
Showing 9 changed files with 825 additions and 185 deletions.
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

0 comments on commit 753d7db

Please sign in to comment.