stephencelis / SQLite.swift

A type-safe, Swift-language layer over SQLite3.
MIT License
9.57k stars 1.55k forks source link

Add column aliasing #1081

Open adamwulf opened 2 years ago

adamwulf commented 2 years ago

Allows Row objects to be created publicly. This makes it easy to convert statements prepared from raw SQL and run with Connection.run() into the same shape as Connection.prepare(anyTableObject)

jberkel commented 2 years ago

Why are there 2 pull requests?

adamwulf commented 2 years ago

I could make them one request if you like, i wasn't sure if both would be accepted

jberkel commented 2 years ago

Was just confused because it looked like both PRs had the same changes, but yes, having it in one would make reviewing simpler.

adamwulf commented 2 years ago

You're completely right - I had messed up the PR and included commits in both. i've closed out the other PR, as this one has everything I've done. Thanks!

jberkel commented 2 years ago

Is your use case aliasing aggregate functions as well? The normal API usage would be something like this:

let name = Expression<String?>("name")
let age = Expression<Int>("age")

let maxAge = age.max
for row in db.prepare(users.select(maxAge).group(name)) {
    print(row[maxAge])
}

And you'd like to write

let maxAge = age.max.alias("maxAge")
for row in db.prepare(users.select(maxAge).group(name)) {
    print(row[maxAge])
}

?

adamwulf commented 2 years ago

I'm sending query results to a process(results) sort of function that expects to have rows with column names A, B, C, etc. In some cases, i can SELECT A, B, C from foo (or foo.select(A, B, C)). This gives rows that have known column names. in other cases, i'd like to SELECT A, B, max(C) AS C GROUP BY A, B and send those query results to the process() function too. without the alias, column C doesn't exist since it's named something else by default.

Specifically, the aggregate is happening inside a VIEW that's replicating the structure of the underlying table. I'd like to select from either the table or the aggregate view and get the exact same column names back.

With the existing API, the column name for that aggregate method is max(C) instead of just C. Being able to control the column name of the resulting rows is helpful for consistency.

jberkel commented 2 years ago

The problem is that the string alias loses type safety, so it's not possible to do this:

let maxAge = age.max.alias("maxAge")

for row in db.prepare(users.select(maxAge).group(name)) {
    print(row[maxAge])
}
adamwulf commented 2 years ago

ah interesting. i'm using it to define a view and i alias to an existing column name, and then use that column object to fetch, so something like:

let myView = view.create(mumbleTable.select(colClock.max.alias("existingColumn"), colStuff).group(colStuff))

... then later ...

for row in db.prepare(myView.select(...)) {
    print(row[existingColumn])
}

I'll have time this weekend to look deeper into this and see what to do about the type safety side of it.

liyuan116 commented 1 year ago

let myView = view.create(mumbleTable.select(colClock.max.alias("existingColumn"), colStuff).group(colStuff))

hi @adamwulf if you delete mumbleTable then create a new table renamed mumbleTable the view myView is work ok?