@@ -88,10 +88,11 @@ extension Language2 {
8888 } . joined ( )
8989
9090 return GeneratedQuery (
91- name: name. description ,
91+ name: " \( name. capitalizedFirst ) Query " ,
9292 type: type,
9393 input: input,
9494 output: output,
95+ outputCardinality: statement. outputCardinality,
9596 sourceSql: sql,
9697 isReadOnly: statement. isReadOnly
9798 )
@@ -126,7 +127,7 @@ extension Language2 {
126127 )
127128 }
128129
129- let inputTypeName = " \( name . capitalizedFirst ) Input "
130+ let inputTypeName = " Input "
130131
131132 let model = GeneratedModel (
132133 name: inputTypeName,
@@ -163,7 +164,7 @@ extension Language2 {
163164 return . builtin( builtinType ( for: firstColumn) , isArray: firstColumn. isRow)
164165 }
165166
166- let outputTypeName = " \( name . capitalizedFirst ) Output "
167+ let outputTypeName = " Row "
167168
168169 let model = GeneratedModel (
169170 name: outputTypeName,
@@ -183,352 +184,6 @@ extension Language2 {
183184 }
184185}
185186
186- public struct SwiftLanguage : Language2 {
187- public static func interpolatedQuestionMarks( for param: String ) -> String {
188- return " \\ ( \( param) .sqlQuestionMarks) "
189- }
190-
191- public static func builtinType( for type: Type ) -> String {
192- return switch type {
193- case let . nominal( name) :
194- switch name. uppercased ( ) {
195- case " REAL " : " Double "
196- case " INT " : " Int "
197- case " INTEGER " : " Int "
198- case " TEXT " : " String "
199- default : " Any "
200- }
201- case let . optional( ty) : " \( builtinType ( for: ty) ) ? "
202- case let . row( . unknown( ty) ) : " [ \( builtinType ( for: ty) ) ] "
203- case . var, . fn, . row, . error: " Any "
204- case . alias( _, let alias) : alias. description
205- }
206- }
207-
208- public static func file(
209- migrations: [ String ] ,
210- tables: [ GeneratedModel ] ,
211- queries: [ GeneratedQuery ] ,
212- options: GenerationOptions
213- ) throws -> String {
214- let file = try SourceFileSyntax {
215- try ImportDeclSyntax ( " import Foundation " )
216- try ImportDeclSyntax ( " import Feather " )
217-
218- for table in tables {
219- try declaration ( for: table, isOutput: true , options: options)
220- }
221-
222- try EnumDeclSyntax ( " enum DB " ) {
223- try declaration ( for: migrations, options: options)
224-
225- for query in queries {
226- if case let . model( input) = query. input, !input. isTable {
227- try declaration ( for: input, isOutput: false , options: options)
228- }
229-
230- if case let . model( output) = query. output, !output. isTable {
231- try declaration ( for: output, isOutput: true , options: options)
232- }
233-
234- try declaration ( for: query, options: options)
235- }
236- }
237-
238- for query in queries {
239- try typealiasFor ( query: query)
240-
241- if case let . model( input) = query. input {
242- try extensionForInput ( query: query, input: input)
243- }
244- }
245- }
246-
247- return file. formatted ( ) . description
248- }
249-
250- public static func queryType(
251- for cardinality: Cardinality ? ,
252- input: BuiltinOrGenerated ? ,
253- output: BuiltinOrGenerated ?
254- ) -> String {
255- let input = input? . description ?? " () "
256- let output = output? . description ?? " () "
257-
258- return switch cardinality {
259- case . single: " FetchSingleQuery< \( input) , \( output) > "
260- case . many: " FetchManyQuery< \( input) , \( output) > "
261- default : " VoidQuery< \( input) > "
262- }
263- }
264-
265- private static func declaration(
266- for migrations: [ String ] ,
267- options: GenerationOptions
268- ) throws -> DeclSyntax {
269- let variable = try VariableDeclSyntax ( " static var migrations: [String] " ) {
270- ArrayExprSyntax (
271- expressions: migrations. map { source in
272- SwiftSyntax . ExprSyntax ( stringLiteral ( of: source, multiline: true )
273- . with ( \. trailingTrivia, . newline) )
274- }
275- )
276- }
277-
278- return DeclSyntax ( variable)
279- }
280-
281- private static func declaration(
282- for query: GeneratedQuery ,
283- options: GenerationOptions
284- ) throws -> DeclSyntax {
285- let query = try VariableDeclSyntax ( " static var \( raw: query. name) : \( raw: query. type) " ) {
286- FunctionCallExprSyntax (
287- calledExpression: DeclReferenceExprSyntax (
288- baseName: . identifier( query. type)
289- ) ,
290- leftParen: . leftParenToken( ) ,
291- arguments: LabeledExprListSyntax {
292- LabeledExprSyntax (
293- label: nil ,
294- colon: nil ,
295- expression: DeclReferenceExprSyntax (
296- baseName: query. isReadOnly ? " .read " : " .write "
297- ) ,
298- trailingComma: nil
299- )
300- } ,
301- rightParen: . rightParenToken( ) ,
302- trailingClosure: ClosureExprSyntax (
303- signature: ClosureSignatureSyntax (
304- parameterClause: . simpleInput( . init {
305- ClosureShorthandParameterSyntax ( name: " input " )
306- ClosureShorthandParameterSyntax ( name: " transaction " )
307- } )
308- )
309- ) {
310- let sql = stringLiteral ( of: query. sourceSql, multiline: true )
311- let statementBinding : TokenSyntax = . keyword( query. input == nil ? . let : . var)
312- " \( statementBinding) statement = try Feather.Statement( \( sql) , \n transaction: transaction \n ) "
313-
314- if let input = query. input {
315- switch input {
316- case let . builtin( _, isArray) :
317- bind ( field: nil , isArray: isArray)
318- case . model( let model) :
319- for field in model. fields. values {
320- bind ( field: field. name, isArray: field. isArray)
321- }
322- }
323- }
324-
325- " return statement "
326- }
327- )
328- }
329-
330- return DeclSyntax ( query)
331- }
332-
333- private static func declaration(
334- for model: GeneratedModel ,
335- isOutput: Bool ,
336- options: GenerationOptions
337- ) throws -> DeclSyntax {
338- let inheretance = InheritanceClauseSyntax {
339- InheritedTypeSyntax ( type: TypeSyntax ( " Hashable " ) )
340-
341- if model. fields [ " id " ] != nil {
342- InheritedTypeSyntax ( type: TypeSyntax ( " Identifiable " ) )
343- }
344-
345- if isOutput {
346- InheritedTypeSyntax ( type: TypeSyntax ( " RowDecodable " ) )
347- }
348- }
349-
350- let strct = StructDeclSyntax (
351- name: TokenSyntax . identifier ( model. name) ,
352- inheritanceClause: inheretance
353- ) {
354- for field in model. fields. values {
355- variableDecl ( name: field. name, type: field. type)
356- }
357-
358- if isOutput {
359- rowDecodableInit ( for: model)
360- memberwiseInit ( for: model)
361- }
362- }
363-
364- return DeclSyntax ( strct)
365- }
366-
367- private static func rowDecodableInit(
368- for model: GeneratedModel
369- ) -> InitializerDeclSyntax {
370- return InitializerDeclSyntax (
371- signature: FunctionSignatureSyntax (
372- parameterClause: FunctionParameterClauseSyntax (
373- parameters: [
374- FunctionParameterSyntax (
375- firstName: " row " ,
376- type: IdentifierTypeSyntax ( name: " borrowing Feather.Row " )
377- )
378- ]
379- ) ,
380- effectSpecifiers: FunctionEffectSpecifiersSyntax (
381- throwsClause: ThrowsClauseSyntax (
382- throwsSpecifier: TokenSyntax . keyword ( . throws) ,
383- leftParen: TokenSyntax . leftParenToken ( ) ,
384- type: TypeSyntax ( " FeatherError " ) ,
385- rightParen: TokenSyntax . rightParenToken ( )
386- )
387- )
388- )
389- ) {
390- " var columns = row.columnIterator() "
391-
392- for field in model. fields. values {
393- " self. \( raw: field. name) = try columns.next() "
394- }
395- }
396- }
397-
398- /// Generates a memberwise initializer for the model
399- private static func memberwiseInit(
400- for model: GeneratedModel
401- ) -> InitializerDeclSyntax {
402- return InitializerDeclSyntax (
403- signature: FunctionSignatureSyntax (
404- parameterClause: FunctionParameterClauseSyntax (
405- parameters: FunctionParameterListSyntax (
406- model. fields. values. positional ( )
407- . map { ( position, field) in
408- FunctionParameterSyntax (
409- firstName: . identifier( field. name) ,
410- type: IdentifierTypeSyntax ( name: . identifier( field. type) ) ,
411- trailingComma: position == . last ? nil : TokenSyntax . commaToken ( )
412- )
413- }
414- )
415- )
416- )
417- ) {
418- for field in model. fields. values {
419- " self. \( raw: field. name) = \( raw: field. name) "
420- }
421- }
422- }
423-
424- /// Generates a typealias for the query so the Query<Input, Output> does
425- /// not have to be typed everytime it's referenced since it can get quite long
426- /// and repetitive.
427- private static func typealiasFor( query: GeneratedQuery ) throws -> TypeAliasDeclSyntax {
428- let name = " \( query. name. capitalizedFirst) Query "
429- let input = query. input. map { " DB. \( $0. description) " } ?? " () "
430- let output = query. output. map { " DB. \( $0. description) " } ?? " () "
431- return try TypeAliasDeclSyntax ( " typealias \( raw: name) = any Query< \( raw: input) , \( raw: output) > " )
432- }
433-
434- /// Creates an extension that has the input struct fields deconstructed so the
435- /// input struct does not have to be constructed every time.
436- private static func extensionForInput(
437- query: GeneratedQuery ,
438- input: GeneratedModel
439- ) throws -> ExtensionDeclSyntax {
440- try ExtensionDeclSyntax ( " extension Query where Input == DB. \( raw: input. name) " ) {
441- let parameters = input. fields. map { parameter in
442- " \( parameter. key) : \( parameter. value. type) "
443- } . joined ( separator: " , " )
444-
445- let args = input. fields. map { parameter in
446- " \( parameter. key) : \( parameter. key) "
447- } . joined ( separator: " , " )
448-
449- """
450- func execute( \( raw: parameters) ) async throws -> Output {
451- try await execute(with: DB. \( raw: input. name) ( \( raw: args) ))
452- }
453- """
454- }
455- }
456-
457- private static func variableDecl(
458- binding: Keyword = . let,
459- name: String ,
460- type: String
461- ) -> VariableDeclSyntax {
462- VariableDeclSyntax (
463- . let,
464- name: " \( raw: name) " ,
465- type: TypeAnnotationSyntax (
466- type: IdentifierTypeSyntax ( name: . identifier( type) )
467- )
468- )
469- }
470-
471- private static func stringLiteral(
472- of contents: String ,
473- multiline: Bool = false
474- ) -> StringLiteralExprSyntax {
475- let openingQuote : TokenSyntax = multiline
476- ? . multilineStringQuoteToken( trailingTrivia: . newline)
477- : . singleQuoteToken( )
478-
479- let closingQuote : TokenSyntax = multiline
480- ? . multilineStringQuoteToken( leadingTrivia: . newline)
481- : . singleQuoteToken( )
482-
483- let segments : StringLiteralSegmentListSyntax
484- if multiline {
485- let lines = contents. split ( separator: " \n " )
486-
487- segments = StringLiteralSegmentListSyntax (
488- lines
489- . enumerated ( )
490- . map { ( i, s) in
491- . stringSegment( StringSegmentSyntax (
492- content: . stringSegment( s. description) ,
493- trailingTrivia: i == lines. count - 1 ? nil : . newline
494- ) )
495- }
496- )
497- } else {
498- segments = [ . stringSegment( . init( content: . stringSegment( contents) ) ) ]
499- }
500-
501- return StringLiteralExprSyntax (
502- leadingTrivia: multiline ? Trivia . newline : nil ,
503- openingQuote: openingQuote,
504- segments: segments,
505- closingQuote: closingQuote,
506- trailingTrivia: nil // Seems like a newline is added automatically?
507- )
508- }
509-
510- @CodeBlockItemListBuilder
511- private static func bind(
512- field: String ? ,
513- isArray: Bool
514- ) -> CodeBlockItemListSyntax {
515- let paramName = if let field {
516- " input. \( field) "
517- } else {
518- " input "
519- }
520-
521- if isArray {
522- """
523- for element in \( raw: paramName) {
524- try statement.bind(value: element)
525- }
526- """
527- } else {
528- " try statement.bind(value: \( raw: paramName) ) "
529- }
530- }
531- }
532187
533188public typealias GenerationOptions = Set < GenerationOption >
534189
@@ -555,6 +210,7 @@ public struct GeneratedQuery {
555210 let type : String
556211 let input : BuiltinOrGenerated ?
557212 let output : BuiltinOrGenerated ?
213+ let outputCardinality : Cardinality
558214 let sourceSql : String
559215 let isReadOnly : Bool
560216}
0 commit comments