@@ -8,17 +8,98 @@ import StructuredQueriesCore
88//
99// Array construction and concatenation functions for building and modifying arrays.
1010
11+ // MARK: - Extension Methods (Swifty API)
12+
13+ extension QueryExpression where QueryValue: Collection , QueryValue. Element: QueryBindable {
14+ /// Appends an element to the end of an array
15+ ///
16+ /// PostgreSQL's `array_append(anyarray, anyelement)` function.
17+ ///
18+ /// ```swift
19+ /// Post.select { $0.tags.appending("swift") }
20+ /// // SELECT array_append("posts"."tags", 'swift') FROM "posts"
21+ /// ```
22+ ///
23+ /// - Parameter element: The element to append to the array
24+ /// - Returns: A new array with the element appended
25+ public func appending( _ element: QueryValue . Element ) -> some QueryExpression < QueryValue > {
26+ SQLQueryExpression (
27+ " array_append( \( self . queryFragment) , \( bind: element) ) " ,
28+ as: QueryValue . self
29+ )
30+ }
31+
32+ /// Prepends an element to the beginning of an array
33+ ///
34+ /// PostgreSQL's `array_prepend(anyelement, anyarray)` function.
35+ ///
36+ /// ```swift
37+ /// Post.select { $0.tags.prepending("featured") }
38+ /// // SELECT array_prepend('featured', "posts"."tags") FROM "posts"
39+ /// ```
40+ ///
41+ /// - Parameter element: The element to prepend to the array
42+ /// - Returns: A new array with the element prepended
43+ public func prepending( _ element: QueryValue . Element ) -> some QueryExpression < QueryValue > {
44+ SQLQueryExpression (
45+ " array_prepend( \( bind: element) , \( self . queryFragment) ) " ,
46+ as: QueryValue . self
47+ )
48+ }
49+
50+ /// Concatenates another array to this array
51+ ///
52+ /// PostgreSQL's `array_cat(anyarray, anyarray)` function.
53+ ///
54+ /// ```swift
55+ /// Post.select { $0.tags.concatenating($0.categories) }
56+ /// // SELECT array_cat("posts"."tags", "posts"."categories") FROM "posts"
57+ /// ```
58+ ///
59+ /// - Parameter other: The array to concatenate
60+ /// - Returns: A new array containing elements from both arrays
61+ public func concatenating( _ other: some QueryExpression < QueryValue > ) -> some QueryExpression < QueryValue > {
62+ SQLQueryExpression (
63+ " array_cat( \( self . queryFragment) , \( other. queryFragment) ) " ,
64+ as: QueryValue . self
65+ )
66+ }
67+
68+ /// Concatenates a Swift array literal to this array
69+ ///
70+ /// PostgreSQL's `array_cat(anyarray, anyarray)` function with literal array.
71+ ///
72+ /// ```swift
73+ /// Post.select { $0.tags.concatenating(["swift", "postgres"]) }
74+ /// // SELECT array_cat("posts"."tags", ARRAY['swift', 'postgres']) FROM "posts"
75+ /// ```
76+ ///
77+ /// - Parameter elements: The elements to concatenate
78+ /// - Returns: A new array containing elements from both arrays
79+ public func concatenating( _ elements: [ QueryValue . Element ] ) -> some QueryExpression < QueryValue > {
80+ // Build array using proper binding for each element
81+ var fragments : [ QueryFragment ] = [ ]
82+ for element in elements {
83+ fragments. append ( " \( bind: element) " )
84+ }
85+ let arrayLiteral = " ARRAY[ \( fragments. joined ( separator: " , " ) ) ] "
86+ return SQLQueryExpression (
87+ " array_cat( \( self . queryFragment) , \( raw: arrayLiteral) ) " ,
88+ as: QueryValue . self
89+ )
90+ }
91+ }
92+
93+ // MARK: - Free Functions (For composing without receiver)
94+
1195/// Appends an element to the end of an array
1296///
1397/// PostgreSQL's `array_append(anyarray, anyelement)` function.
1498///
1599/// ```swift
16- /// Post .select { $0.tags.append( "swift") }
17- /// // SELECT array_append("posts "."tags", 'swift') FROM "posts "
100+ /// User .select { append( $0.tags, "swift") }
101+ /// // SELECT array_append("users "."tags", 'swift') FROM "users "
18102/// ```
19- ///
20- /// - Parameter element: The element to append to the array
21- /// - Returns: A new array with the element appended
22103public func append< Element> (
23104 _ array: some QueryExpression < [ Element ] > ,
24105 _ element: Element
@@ -29,71 +110,33 @@ public func append<Element>(
29110 )
30111}
31112
32- /// Appends an element to the end of an array (method syntax)
33- extension QueryExpression where QueryValue: Collection , QueryValue. Element: QueryBindable {
34- /// Appends an element to the end of an array
35- ///
36- /// ```swift
37- /// Post.select { $0.tags.append("swift") }
38- /// // SELECT array_append("posts"."tags", 'swift') FROM "posts"
39- /// ```
40- public func append( _ element: QueryValue . Element ) -> some QueryExpression < QueryValue > {
41- SQLQueryExpression (
42- " array_append( \( self . queryFragment) , \( bind: element) ) " ,
43- as: QueryValue . self
44- )
45- }
46- }
47-
48113/// Prepends an element to the beginning of an array
49114///
50115/// PostgreSQL's `array_prepend(anyelement, anyarray)` function.
51116///
52117/// ```swift
53- /// Post .select { $0.tags.arrayPrepend ("featured") }
54- /// // SELECT array_prepend('featured', "posts "."tags") FROM "posts "
118+ /// User .select { prepend ("featured", to: $0.tags ) }
119+ /// // SELECT array_prepend('featured', "users "."tags") FROM "users "
55120/// ```
56- ///
57- /// - Parameter element: The element to prepend to the array
58- /// - Returns: A new array with the element prepended
59- public func arrayPrepend< Element> (
121+ public func prepend< Element> (
60122 _ element: Element ,
61- _ array: some QueryExpression < [ Element ] >
123+ to array: some QueryExpression < [ Element ] >
62124) -> some QueryExpression < [ Element ] > where Element: QueryBindable {
63125 SQLQueryExpression (
64126 " array_prepend( \( bind: element) , \( array. queryFragment) ) " ,
65127 as: [ Element ] . self
66128 )
67129}
68130
69- /// Prepends an element to the beginning of an array (method syntax)
70- extension QueryExpression where QueryValue: Collection , QueryValue. Element: QueryBindable {
71- /// Prepends an element to the beginning of an array
72- ///
73- /// ```swift
74- /// Post.select { $0.tags.arrayPrepend("featured") }
75- /// // SELECT array_prepend('featured', "posts"."tags") FROM "posts"
76- /// ```
77- public func arrayPrepend( _ element: QueryValue . Element ) -> some QueryExpression < QueryValue > {
78- SQLQueryExpression (
79- " array_prepend( \( bind: element) , \( self . queryFragment) ) " ,
80- as: QueryValue . self
81- )
82- }
83- }
84-
85131/// Concatenates two arrays
86132///
87133/// PostgreSQL's `array_cat(anyarray, anyarray)` function.
88134///
89135/// ```swift
90- /// Post .select { $0.tags.arrayCat(["swift", "postgres"] ) }
91- /// // SELECT array_cat("posts "."tags", ARRAY['swift', 'postgres'] ) FROM "posts "
136+ /// User .select { concatenate( $0.tags, $0.categories ) }
137+ /// // SELECT array_cat("users "."tags", "users"."categories" ) FROM "users "
92138/// ```
93- ///
94- /// - Parameter other: The array to concatenate
95- /// - Returns: A new array containing elements from both arrays
96- public func arrayCat< Element> (
139+ public func concatenate< Element> (
97140 _ array1: some QueryExpression < [ Element ] > ,
98141 _ array2: some QueryExpression < [ Element ] >
99142) -> some QueryExpression < [ Element ] > where Element: QueryBindable {
@@ -103,55 +146,29 @@ public func arrayCat<Element>(
103146 )
104147}
105148
106- /// Concatenates two arrays (method syntax)
107- extension QueryExpression where QueryValue: Collection , QueryValue. Element: QueryBindable {
108- /// Concatenates another array to this array
109- ///
110- /// ```swift
111- /// Post.select { $0.tags.arrayCat(["swift", "postgres"]) }
112- /// // SELECT array_cat("posts"."tags", ARRAY['swift', 'postgres']) FROM "posts"
113- /// ```
114- public func arrayCat( _ other: [ QueryValue . Element ] ) -> some QueryExpression < QueryValue > {
115- let arrayLiteral = " ARRAY[ \( other. map { " ' \( $0) ' " } . joined ( separator: " , " ) ) ] "
116- return SQLQueryExpression (
117- " array_cat( \( self . queryFragment) , \( raw: arrayLiteral) ) " ,
118- as: QueryValue . self
119- )
120- }
121-
122- /// Concatenates another array expression to this array
123- ///
124- /// ```swift
125- /// Post.select { $0.tags.arrayCat($0.categories) }
126- /// // SELECT array_cat("posts"."tags", "posts"."categories") FROM "posts"
127- /// ```
128- public func arrayCat( _ other: some QueryExpression < QueryValue > ) -> some QueryExpression <
129- QueryValue
130- > {
131- SQLQueryExpression (
132- " array_cat( \( self . queryFragment) , \( other. queryFragment) ) " ,
133- as: QueryValue . self
134- )
135- }
136- }
149+ // MARK: - Array Constructors
137150
138151/// Creates an array from the given elements
139152///
140153/// PostgreSQL's ARRAY constructor syntax.
141154///
142155/// ```swift
143- /// let tags = arrayFrom (["swift", "postgres", "server"])
156+ /// let tags = array (["swift", "postgres", "server"])
144157/// Post.insert { Post.Draft(title: "Hello", tags: tags) }
145158/// // INSERT INTO "posts" ("title", "tags") VALUES ('Hello', ARRAY['swift', 'postgres', 'server'])
146159/// ```
147160///
148161/// - Parameter elements: The elements to create an array from
149162/// - Returns: An array expression
150- public func arrayFrom < Element> (
163+ public func array < Element> (
151164 _ elements: [ Element ]
152165) -> some QueryExpression < [ Element ] > where Element: QueryBindable {
153- let arrayLiteral = QueryFragment (
154- " ARRAY[ \( raw: elements. map { " ' \( $0) ' " } . joined ( separator: " , " ) ) ] " )
166+ // Build array using proper binding for each element
167+ var fragments : [ QueryFragment ] = [ ]
168+ for element in elements {
169+ fragments. append ( " \( bind: element) " )
170+ }
171+ let arrayLiteral = QueryFragment ( " ARRAY[ \( fragments. joined ( separator: " , " ) ) ] " )
155172 return SQLQueryExpression ( arrayLiteral, as: [ Element ] . self)
156173}
157174
@@ -160,42 +177,18 @@ public func arrayFrom<Element>(
160177/// PostgreSQL's empty ARRAY constructor.
161178///
162179/// ```swift
163- /// Post.insert { Post.Draft(title: "Hello", tags: emptyArray(String.self)) }
180+ /// Post.insert { Post.Draft(title: "Hello", tags: emptyArray(of: String.self)) }
164181/// // INSERT INTO "posts" ("title", "tags") VALUES ('Hello', ARRAY[]::text[])
165182/// ```
166183///
167184/// - Parameter elementType: The type of elements in the array
168185/// - Returns: An empty array expression
169186public func emptyArray< Element> (
170- _ elementType: Element . Type
171- ) -> some QueryExpression < [ Element ] > where Element: QueryBindable {
172- // PostgreSQL requires type cast for empty arrays
173- let pgType = postgresTypeName ( for: elementType)
174- return SQLQueryExpression ( " ARRAY[]:: \( raw: pgType) [] " , as: [ Element ] . self)
175- }
176-
177- // Helper to map Swift types to PostgreSQL type names
178- private func postgresTypeName< T> ( for type: T . Type ) -> String {
179- switch type {
180- case is String . Type , is String ? . Type:
181- return " text "
182- case is Int . Type , is Int ? . Type:
183- return " integer "
184- case is Int64 . Type , is Int64 ? . Type:
185- return " bigint "
186- case is Double . Type , is Double ? . Type:
187- return " double precision "
188- case is Float . Type , is Float ? . Type:
189- return " real "
190- case is Bool . Type , is Bool ? . Type:
191- return " boolean "
192- case is UUID . Type , is UUID ? . Type:
193- return " uuid "
194- case is Date . Type , is Date ? . Type:
195- return " timestamp "
196- case is Data . Type , is Data ? . Type:
197- return " bytea "
198- default :
199- return " text " // Fallback to text
200- }
201- }
187+ of elementType: Element . Type
188+ ) -> some QueryExpression < [ Element ] > where Element: PostgreSQLType {
189+ // Use PostgreSQLType protocol for type-safe type name resolution
190+ return SQLQueryExpression (
191+ " ARRAY[]:: \( raw: Element . typeName) [] " ,
192+ as: [ Element ] . self
193+ )
194+ }
0 commit comments