Skip to content

Releases: SwifQL/Bridges

🍇 Implement `TableUpsert`

05 Nov 04:14
Compare
Choose a tag to compare
Pre-release

Thanks to @EthanLozano for the inspiration ⚡️

We already have conveniences for insertInto and update and obviously upsert is needed too! And now we have it!

let newUser = User(id: UUID(), email: "[email protected]", firstName: "Ethan", lastName: "Lozano")

/// we could simply insert it
newUser.insert(on: conn)

/// we could simply update it
newUser.update(on: \.$id, on: conn)

/// now we can upsert it on conflicting column
newUser.upsert(conflictColumn: \.$id, on: conn)
newUser.upsert(conflictColumn: \.$email, on: conn)
/// we even can exclude one or more columns from update, e.g. excluding id may be necessary for update by email
newUser.upsert(conflictColumn: \.$email, excluding: \User.$id, on: conn)

/// we also can upsert on conflicting constraint
newUser.upsert(conflictConstraint: "User_pkey", on: conn)
newUser.upsert(conflictConstraint: "User_email_key", on: conn)
newUser.upsert(conflictConstraint: "User_email_key", excluding: \User.$id, on: conn)

🏂 Decode custom model even with convenience method

01 Nov 01:35
Compare
Choose a tag to compare

Let's say you have a Goods table and you want to query a list of goods but only their id and name columns using this simple model

struct ListModel: Content {
    let id: UUID
    let name: String
}

We have simple and convenient method for querying all records from table in Bridges

Goods.query(on: .psql, on: req).all()

but the problem in current situation is that it returns exactly Goods model and we have to do additional for-loop for its transformation

Ok, since we're advanced SwifQL users we will use it to decode exactly into ListModel

SwifQL.select(Goods.table.*).from(Goods.table).execute(on: .psql, on: req).all(decoding: ListModel.self)

and it's cool, but too long as for me

New addition to first convenience method solves this problem

Goods.query(on: .psql, on: req).all(decoding: ListModel.self)

That's it 😃

💫 Enum array support

26 Oct 06:16
Compare
Choose a tag to compare
Pre-release

Now you're able to use enum array columns in your tables!

Preparation

enum Permission: String, BridgesEnum {    
    case create, edit, delete
}

final class User: Table, Content {
    @Column("id")
    var id: UUID

    @Column("permissions")
    var permissions: [Permission]

    init () {}
    init(permissions: [Permission] = []) {
        self.id = UUID()
        self.permissions = permissions
    }
}

struct CreateUserMigration: TableMigration {
    typealias Table = User
        
    static func prepare(on conn: BridgeConnection) -> EventLoopFuture<Void> {
        createBuilder
            .column("id", .uuid, .primaryKey)
            .column("permissions", .auto(from: [Permission].self), .default("{}"), .notNull)
            .execute(on: conn)
    }
        
    static func revert(on conn: BridgeConnection) -> EventLoopFuture<Void> {
        dropBuilder.execute(on: conn)
    }
}

Usage examples

Where clause

SELECT * FROM "User" WHERE "permissions" @> '{edit}'
SwifQL.select(User.table.*).from(User.table).where(\User.$permissions ||> [Permission.edit])

Convenient update

// let user: User // already fetched user
user.permissions = [.create, .edit, .delete]
user.update(on: \.$id, on: conn)

Pure update

UPDATE "User" SET "permissions" = '{edit,delete}' WHERE "permissions" @> '{edit}'
SwifQL.update(User.table)
      .set[items: \User.$permissions == [Permission.edit, .delete]]
      .where(\User.$permissions ||> [Permission.edit])

Convenient insert

User(permissions: [.create, .edit, .delete]).insert(on: conn)

Pure insert

INSERT INTO "User" ("id", "permissions") VALUES (..., '{create,edit,delete}')
SwifQL.insertInto(User.table, fields: \User.$id, \User.$permissions)
      .values
      .values(UUID(), [Permission.create, .edit, .delete])
      .execute(on: conn)

🍓SwifQLable: Implement convenient execute method

20 May 10:34
Compare
Choose a tag to compare

When you have only one complex query in route handler and it shouldn't be wrapped into transaction you could use this convenient method which will establish database connection by itself

func someComplexQueryHandler(_ req: Request) throws -> EventLoopFuture<[CustomResult]> {
    SwifQL

        /// your complex query here

        .execute(.psqlEnvironment, on: req)
        .all() // or explicit .all(decoding: CustomResult.self)
        // .first()  // or explicit .first(decoding: CustomResult.self)
        // .count()
}

More conveniences

19 May 11:40
Compare
Choose a tag to compare
More conveniences Pre-release
Pre-release

Examples below shows how to use new conveniences with demo User table

/// without connection, useful if you have only one query there
func databaseRequest(on req: Request) -> EventLoopFuture<SomeResult> {
    User.query(on: .psqlEnvironment, on: req)

            // optionally use `where` clause with all SwifQL's power
            .where(\User.$email == "[email protected]")

            /// also available
           // .join(...)
           // .groupBy(...)
           // .having(...)
           // .orderBy(...)
           // .offset(...)
           // .limit(...)

           /// and select one of methods listed below to get the result
           .first() // to get `EventLoopFuture<User?>`
           .all() // to get `EventLoopFuture<[User]>`
           .count() // to get EventLoopFuture<Int64>
           .delete() // to execute DELETE FROM "User" query, returns Void
}

/// with connection
func databaseRequest(on req: Request) -> EventLoopFuture<[User]> {
    req.postgres.connection(to: .psqlEnvironment) { conn in
        // you could build query the same way as shown in above example
        User.query(on: conn).all()
    }
}

💡 available for all dialects

1.0.0-rc.4.0.0

08 May 23:25
3d5958e
Compare
Choose a tag to compare
1.0.0-rc.4.0.0 Pre-release
Pre-release

I've pimped DatabaseIdentifier to use it for conveniences.

It is not breaking change, but if someone is using custom DatabaseIdentifier in case if he will try to use new conveniences it will throw an error and print a message in console that he should use explicit initialization for identifier.

So e.g. this

extension DatabaseIdentifier {
    static var dev_psql: DatabaseIdentifier {
        .init(name: "dev_test", host: DatabaseHost(hostname: "127.0.0.1", port: 5432, username: "root", password: "qwerty"), maxConnectionsPerEventLoop: 1)
    }
}

should be replaced with

extension DatabaseIdentifier {
    static var dev_psql: DatabaseIdentifier {
        .psql(name: "dev_test", host: DatabaseHost(hostname: "127.0.0.1", port: 5432, username: "root", password: "qwerty"), maxConnectionsPerEventLoop: 1)
    }
}

Conveniences

MyTable.all(on: .psqlEnvironment, on: req) // or on: application
MyTable.first(on: .psqlEnvironment, on: req) // or on: application

Later I gonna use this fundament for generic select builder which I want to be available the same way like

Table.where(\.$name == "John").all(on: .psqlEnvironment, on: req)

AnyMigration: replace `name` with `migrationName`

06 May 21:45
b971684
Compare
Choose a tag to compare

Since we use tableName for tables we realized that we have to use migrationName for migrations for consistency.

name will still work since it is release candidate already 🙂

TableInsert: implement batch insert

11 Feb 12:55
Compare
Choose a tag to compare
let user1 = User(...)
let user2 = User(...)
let user3 = User(...)
[user1, user2, user3].batchInsert(on: conn) // returns EventLoopFuture<Void>