[Pitch] Swift Cassandra Client

Hello,

We would like to pitch the Swift Cassandra Client package for inclusion in the SSWG incubation process. The library has not yet reached version 1.0, but since it has been used successfully at Apple, we propose to include it in the Incubating maturity level.

Package Description

The Swift Cassandra Client package is a Cassandra database client based on DataStax Cassandra C++ Driver, wrapping it with Swift-friendly APIs and data structures.

It offers API based on SwiftNIO futures, as well as Swift concurrency based API in Swift 5.5 and newer.

Package Name swift-cassandra-client
Proposed Maturity Level Incubating
License Apache 2.0
Dependencies swift-nio, swift-log

Motivation

Apple is an active contributor to the Apache Cassandra open source project and server teams at Apple use Cassandra to support various use-cases. The Swift Cassandra Client has been used in that capacity for several years and we believe the broader Swift community would find it useful as well.

Usage

Swift concurrency based API

Creating a client instance

var configuration = CassandraClient.Configuration(...)
let cassandraClient = CassandraClient(configuration: configuration)

The client has a default session established (lazily) so that it can be used directly to perform
queries on the configured keyspace:

let result = try await cassandraClient.query(...)

The client must be explicitly shut down when no longer needed:

try cassandraClient.shutdown()

Creating a session for a different keyspace

let session = cassandraClient.makeSession(keyspace: <KEYSPACE>)
let result = try await session.query(...)

The session must be explicitly shut down when no longer needed:

try session.shutdown()

You can also create a session and pass in a closure, which will automatically release the resource when the closure exits:

try await cassandraClient.withSession(keyspace: <KEYSPACE>) { session in
  ...
}

Running result-less commands (e.g. insert, update, delete or DDL)

try await cassandraClient.run("create table ...")

Or at session level:

try await session.run("create table ...")

Running queries returning small datasets that fit in memory

Returning a model object, having Model: Codable:

let result: [Model] = try await cassandraClient.query("select * from table ...")
let result: [Model] = try await session.query("select * from table ...")

Or using free-form transformations on the row:

let values = try await cassandraClient.query("select * from table ...") { row in
  row.column(<COLUMN_NAME>).int32
}
let values = try await session.query("select * from table ...") { row in
  row.column(<COLUMN_NAME>).int32
}

Running queries returning large datasets that do not fit in memory

// `rows` is a sequence that one needs to iterate on
let rows: Rows = try await cassandraClient.query("select * from table ...")
// `rows` is a sequence that one needs to iterate on
let rows: Rows = try await session.query("select * from table ...")

SwiftNIO future based API

Creating a client instance

var configuration = CassandraClient.Configuration(...)
let cassandraClient = CassandraClient(configuration: configuration)

The client has a default session established (lazily) so that it can be used directly to perform
queries on the configured keyspace:

let resultFuture = cassandraClient.query(...)

The client must be explicitly shut down when no longer needed:

try cassandraClient.shutdown()

Creating a session for a different keyspace

let session = cassandraClient.makeSession(keyspace: <KEYSPACE>)
let resultFuture = session.query(...)

The session must be explicitly shut down when no longer needed:

try session.shutdown()

You can also create a session and pass in a closure, which will automatically release the resource when the closure exits:

try cassandraClient.withSession(keyspace: <KEYSPACE>) { session in
  ...
}

Running result-less commands (e.g. insert, update, delete or DDL)

let voidFuture = cassandraClient.run("create table ...")

Or at session level:

let voidFuture = session.run("create table ...")

Running queries returning small datasets that fit in memory

Returning a model object, having Model: Codable:

cassandraClient.query("select * from table ...").map { (result: [Model]) in
  ...
}
session.query("select * from table ...").map { (result: [Model]) in
  ...
}

Or using free-form transformations on the row:

cassandraClient.query("select * from table ...") { row in
  row.column(<COLUMN_NAME>).int32
}.map { value in
  ...
}
session.query("select * from table ...") { row in
  row.column(<COLUMN_NAME>).int32
}.map { value in
  ...
}

Running queries returning large datasets that do not fit in memory

cassandraClient.query("select * from table ...").map { (rows: Rows) in
  // `rows` is a sequence that one needs to iterate on
  rows.map { row in
    ...
  }
}
session.query("select * from table ...").map { (rows: Rows) in
  // `rows` is a sequence that one needs to iterate on
  rows.map { row in
    ...
  }
}

Maturity justification

As the library has been in use at Apple for several years, and we would like more time and exposure to collect user feedback to move towards its 1.0 release, we propose to include Swift Cassandra Client in Incubating level.

12 Likes

Huge +1 for this being added to the SSWG incubated list. This fills a very important gap in the server ecosystem and will be extremely useful.

Personally, I'd prefer to see it pitched at the sandbox level for a couple of reasons:

  • No current stable API - given that it's not at 1.0 yet it's not unreasonable to expect changes coming up which is something we'd prefer to avoid for an incubated project
  • The incubation process states that incubating projects should "Document that it is being used successfully in production by at least two independent end users which" - we probably need to clarify whether Apple counts as one end user or whether multiple teams inside Apple count as multiple users (to be clear I have no doubt about the stability or quality of the package)

One side question - we "prefer native Swift over C wrapping, where appropriate" and the library currently heavily relies on a C++ driver. Are there plans to port this to Swift to make contributing easier (and use a memory safe language)? This definitely isn't a blocker and we have plenty of packages that use C code where it makes sense but would be good to know the long term vision.

Tim

6 Likes

Awesome, thank you for the work on the lib and the pitch, Yim!

Process wise: I'm happy to take care of this proposal and run it through review -- let's give it a few days over here in pitch phase in case people want to chime in more before we run the official review :+1:

Very nice addition.

Question: This is also usable from a non-server application?

Technically I guess you "could" but that's rather ill advised to expose a database over the public internet. I'd really recommend putting a service (e.g. using Vapor, Smoke or Hummingbird) in front of that, rather than exposing a db to the public.

1 Like

This is not my use case, just using a non-server app to reach the database, the database being run locally or behind the same firewall as the app. So no difference to the server app case. I am asking because from what I remember that was a problem with the PostgerSQL package for Vapor, no chance to use it outside a Vapor app, but for your Cassandra client this seems to work (just async/await needed, so not running on Windows, but this is a different topic),

I think the non-server app use case should be considered when doing a database client (if possible).

Then sure, as long as the C driver this lib is wrapping can build on the platform you intend to use this, this will work fine outside of a β€œserver”.

Definitely +1 on moving forward to a proposal, this is a great addition to the ecosystem.

1 Like

This pitch is now under review: SSWG-0021: Swift Cassandra Client

2 Likes