SQLite Swift db.prepare Optional() in statement values - sqlite.swift

In the SQLite Swift documentation there is reference to getting statement results directly. I have a lot of SQL queries prepared and I don't really want to refactor them. I would sooner use them as they are using db.prepare, as per below.
Statements with results may be iterated over.
let stmt = try db.prepare("SELECT id, email FROM users")
for row in stmt {
print("id: \(row[0]), email: \(row[1])")
// id: Optional(1), email: Optional("alice#mac.com")
}
The return values always have the "Optional()" around them. Is there a way we can just get the raw row values back without this?

Unwrap the values using ! after the variable as #stephencelis said:
let stmt = try db.prepare("SELECT id, email FROM users")
for row in stmt {
print("id: \(row[0]!), email: \(row[1]!)")
}

You may want to use https://github.com/groue/GRDB.swift. It lets you extract optionals or non-optionals, just as you wish:
for row in Row.fetch(db, "SELECT id, email FROM users") {
let id: Int64 = row.value(atIndex: 0)
let email: String = row.value(atIndex: 1)
print(id, email)
}

The type-safe API lets you declare expressions of non-optional types that, when pulled back from a statement, are not wrapped.
From the README:
let users = Table("users")
let id = Expression<Int64>("id")
let name = Expression<String?>("name")
let email = Expression<String>("email")
try db.run(users.create { t in
t.column(id, primaryKey: true)
t.column(name)
t.column(email, unique: true)
})
// CREATE TABLE "users" (
// "id" INTEGER PRIMARY KEY NOT NULL,
// "name" TEXT,
// "email" TEXT NOT NULL UNIQUE
// )
let insert = users.insert(name <- "Alice", email <- "alice#mac.com")
let rowid = try db.run(insert)
// INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice#mac.com')
for user in db.prepare(users) {
println("id: \(user[id]), name: \(user[name]), email: \(user[email])")
// id: 1, name: Optional("Alice"), email: alice#mac.com
}
Note that both id and email, which are non-optional, are returned as such.

Related

Insert data and get back id in mybatis

I should save the record in the database and get the record id in the response. After a long search and research I came up with the following option.
data class User(
val id: UUID? = null,
val username: String,
...
)
UserRepo:
#Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
#Insert("""
INSERT INTO "user" (
username,
...
) VALUES (
#{username},
...
)
""")
fun save(user: User): User
in response I get the following.
org.apache.ibatis.binding.BindingException: Mapper method '...UserRepository.save' has an unsupported return type: class ...entity.User
in the following case, I don't get an error, but I don't get an id either, how can I do it correctly? Used Select instead of Insert
#Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
#Select("""
INSERT INTO "user" (
username
) VALUES (
#{username}
)
""")
fun save(user: User): User
if we solve my first question, then I would like to know if it is possible to get the answer not in the User class, but in another one, for example, UserResponse? That is, send a request to the User class, and receive a response in the UserResponse
UPDATE
I was able to get the id after changing the id type to String.
Like this:
data class User(
val id: String? = null,
val username: String,
...
)
Apparently some settings are needed for UUID?
Has anyone faced such a problem?

Android Room Multimap issue for the same column names

As stated in official documentation, it's preferable to use the Multimap return type for the Android Room database.
With the next very simple example, it's not working correctly!
#Entity
data class User(#PrimaryKey(autoGenerate = true) val _id: Long = 0, val name: String)
#Entity
data class Book(#PrimaryKey(autoGenerate = true) val _id: Long = 0, val bookName: String, val userId: Long)
(I believe a loooot of the developers have the _id primary key in their tables)
Now, in the Dao class:
#Query(
"SELECT * FROM user " +
"JOIN book ON user._id = book.userId"
)
fun allUserBooks(): Flow<Map<User, List<Book>>>
The database tables:
Finally, when I run the above query, here is what I get:
While it should have 2 entries, as there are 2 users in the corresponding table.
PS. I'm using the latest Room version at this point, Version 2.4.0-beta02.
PPS. The issue is in how UserDao_Impl.java is being generated:
all the _id columns have the same index there.
Is there a chance to do something here? (instead of switching to the intermediate data classes).
all the _id columns have the same index there.
Is there a chance to do something here?
Yes, use unique column names e.g.
#Entity
data class User(#PrimaryKey(autoGenerate = true) val userid: Long = 0, val name: String)
#Entity
data class Book(#PrimaryKey(autoGenerate = true) valbookid: Long = 0, val bookName: String, val useridmap: Long)
as used in the example below.
or
#Entity
data class User(#PrimaryKey(autoGenerate = true) #ColumnInfo(name="userid")val _id: Long = 0, val name: String)
#Entity
data class Book(#PrimaryKey(autoGenerate = true) #ColumnInfo(name="bookid")val _id: Long = 0, val bookName: String, val #ColumnInfo(name="userid_map")userId: Long)
Otherwise, as you may have noticed, Room uses the value of the last found column with the duplicated name and the User's _id is the value of the Book's _id column.
Using the above and replicating your data using :-
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
var currentUserId = dao.insert(User(name = "Eugene"))
dao.insert(Book(bookName = "Eugene's book #1", useridmap = currentUserId))
dao.insert(Book(bookName = "Eugene's book #2", useridmap = currentUserId))
dao.insert(Book(bookName = "Eugene's book #3", useridmap = currentUserId))
currentUserId = dao.insert(User(name = "notEugene"))
dao.insert(Book(bookName = "not Eugene's book #4", useridmap = currentUserId))
dao.insert(Book(bookName = "not Eugene's book #5", useridmap = currentUserId))
var mapping = dao.allUserBooks() //<<<<<<<<<< BREAKPOINT HERE
for(m: Map.Entry<User,List<Book>> in mapping) {
}
for convenience and brevity a Flow hasn't been used and the above was run on the main thread.
Then the result is what I believe you are expecting :-
Additional
What if we already have the database structure with a lot of "_id" fields?
Then you have some decisions to make.
You could
do a migration to rename columns to avoid the ambiguous/duplicate column names.
use alternative POJO's in conjunction with changing the extract output column names accordingly
e.g. have :-
data class Alt_User(val userId: Long, val name: String)
and
data class Alt_Book (val bookId: Long, val bookName: String, val user_id: Long)
along with :-
#Query("SELECT user._id AS userId, user.name, book._id AS bookId, bookName, user_id " +
"FROM user JOIN book ON user._id = book.user_id")
fun allUserBooksAlt(): Map<Alt_User, List<Alt_Book>>
so user._id is output with the name as per the Alt_User POJO
other columns output specifically (although you could use * as per allUserBookAlt2)
:-
#Query("SELECT *, user._id AS userId, book._id AS bookId " +
"FROM user JOIN book ON user._id = book.user_id")
fun allUserBooksAlt2(): Map<Alt_User, List<Alt_Book>>
same as allUserBooksAlt but also has the extra columns
you would get a warning warning: The query returns some columns [_id, _id] which are not used by any of [a.a.so70190116kotlinroomambiguouscolumnsfromdocs.Alt_User, a.a.so70190116kotlinroomambiguouscolumnsfromdocs.Alt_Book]. You can use #ColumnInfo annotation on the fields to specify the mapping. You can annotate the method with #RewriteQueriesToDropUnusedColumns to direct Room to rewrite your query to avoid fetching unused columns. You can suppress this warning by annotating the method with #SuppressWarnings(RoomWarnings.CURSOR_MISMATCH). Columns returned by the query: _id, name, _id, bookName, user_id, userId, bookId. public abstract java.util.Map<a.a.so70190116kotlinroomambiguouscolumnsfromdocs.Alt_User, java.util.List<a.a.so70190116kotlinroomambiguouscolumnsfromdocs.Alt_Book>> allUserBooksAlt2();
Due to Note that Room will not rewrite the query if it has multiple columns that have the same name as it does not yet have a way to distinguish which one is necessary. the #RewriteQueriesToDropUnusedColumns doesn't do away with the warning.
if using :-
var mapping = dao.allUserBooksAlt() //<<<<<<<<<< BREAKPOINT HERE
for(m: Map.Entry<Alt_User,List<Alt_Book>> in mapping) {
}
Would result in :-
possibly other options.
However, I'd suggest fixing the issue once and for all by using a migration to rename columns to all have unique names. e.g.

Linq to sql, query many-to-many with count of other

I have a many-to-many relation and I'm trying to create the query which will fetch me the left side and a property which counts the number of records which are refferecend by it.
following is my query
var dbSet = await (from user in _dbContext.Users
where (from courseUsers in _dbContext.CourseUsers select courseUsers.UserId).Contains(user.Id)
select new
{
Name = user.Name,
Id = user.Id,
CourseUsersCount = _dbContext.CourseUsers.Where(item => item.UserId == user.Id).Count()
})
.ToListAsync();
What I don't like is how CourseUsersCount is computed. I would also like to include the total count property and the way I would do it is to add another property on the select which would just do a count over the _dbContext.CourseUsers and after that do another in-memory transformation.
I the end I would like a result with this structure to be created
{
count: 1000,
data: [{
Id: 1,
Name: "c",
CourseUsersCount: 2
}]
}
and I want to know how can I do this directly using linq-to-sql.
As mentioned in comments you have to use GroupBy for such calculation:
var query
from user in _dbContext.Users
from courseUsers in user.Courses
group user by new { user.Id, user.Name } into g
select new
{
g.Key.Id,
g.Key.Name,
CourseUsersCount = g.Count()
};
You need a Group By to merge all the CourseUsers into a single set, followed by a Join to attach the Users to it.
from course in _dbContext.CourseUsers // outer sequence
group course by course.UserId into courseGrp
join user in _dbContext.Users //inner sequence
on courseGrp.Key equals user.UserId// key selector
select new { // result selector
CourseUsersCount = courseGrp.Count(),
user.Name,
user.Id
};

Using a key condition expression that includes conditions on the sortkey (AWS DynamoDB with Serverless Framework)

I want to retrieve just ONE item from a DynamoDB table ("todosTable") with partitionKey = userID and sortKey = todoID.
Just to show you something, below there is an example that correctly gets ALL the todo items for a user with userId=userId
async getAllTodos(userId: string): Promise<TodoItem[]>{
const result = await this.docClient.query({
TableName: this.todosTable,
KeyConditionExpression: 'userId = :userId',
ExpressionAttributeValues: {
':userId': userId
}
}).promise()
return result.Items as TodoItem[]
}
But that is not what I want. I want just one item.
As per the documentation, I will use a key condition expression that acts upon Primary keys(partition and/or sort key)
So I tried to do it this way but this is incorrect
async getTodoItem1(userId: string, todoId: string): Promise<TodoItem>{
const result = await this.docClient.get({
TableName: this.todosTable,
KeyConditionExpression: 'userId = :userId',
AND todId = :todoId,
ExpressionAttributeValues: {
':userId': userId,
':todoId': todoId
}
}).promise()
return result.Item
}
Could someone please help me get the correct query that retrieves just one item, where partition key = userID and sort key = todoID ?
Is the correct pattern to use a GlobalSecondaryIndex in this case?
Thanks
You'll need to add the secondary key to the KeyConditionExpression as well, should look something like this:
KeyConditionExpression: 'userId = :userId AND todoId = :todoId'
The sort key is more flexible and allows for other operations like BEGINS_WITH, BETWEEN etc

clean way to get same field by different key

Here is the problem. I can get member by ID and my query looks like below:
{
member(memberId:[1,2]) {
firstName
lastName
contacts {
}
}
}
Now I need to add few more query to get member by name and email like below
{
member(email:["abc#xy.com","adc#xy.com"]) {
firstName
lastName
contacts {
}
}
}
{
member(name:["abc","adc"]) {
firstName
lastName
contacts {
}
}
}
How do I design my graphQL query and schema? Should my query have just 1 field with multiple optional arguments? like below
Field("member", ListType(Member),
arguments = ids :: email :: name,
resolve = (ctx) => {
val id : Seq[Int] = ctx.arg("memberId")
ctx.ctx.getMemberDetails(id)
})
Or should I have multiple query with different field under a schema. like below
Field("memberById", ListType(Member),
arguments = Id :: Nil,
resolve = (ctx) => {
val id : Seq[Int] = ctx.arg("memberId")
ctx.ctx.getMemberDetails(id)
})
Field("memberByEmail", ListType(Member),
arguments = email :: Nil,
resolve = (ctx) => {
val id : Seq[Int] = ctx.arg("memberId")
ctx.ctx.getMemberDetails(id)
})
Field("memberByName", ListType(Member),
arguments = name :: Nil,
resolve = (ctx) => {
val id : Seq[Int] = ctx.arg("memberId")
ctx.ctx.getMemberDetails(id)
})
Thank you in advance. let me know in case you need more details.
You should think about advantanges and disadvantages of both solutions.
If you will prepare separate fields, you will get a lot of boilerplate.
On the other hand you can set all possible inputs as OptionalInputType, it makes schema field only. Disadvantage of this solutions is that Sangria cannot validate a field that at least one argument should be required, so you have to cover this case with proper response or whatever.
The third option is to make generic solution at Schema level. You can create a query with two arguments filterName and filterValues, first would be EnumType for Id, Email, Name, the second would be a list of strings.
Such solution avoid disadvantages of both previous solutions, it has required fields and it doesn't need spreading fields in schema for every filter. Additionally if you want to add any additional function you have only edit FilterName enum and a resolver function to cover this.
Finally you schema will looks like this:
enum FilterName {
ID
EMAIL
NAME
}
type Query {
member(filterName: FilterName!, filterValues: [String]!): Member!
}

Resources