Kotlin Exposed SQL query on Table with compound primary key and select all which are contained in a given List of DTO Objects - kotlin-exposed

considering the following pseudo code:
object EntityTable : Table("ENTITY") {
val uid = uuid("uid")
val idCluster = integer("id_cluster")
val idDataSchema = integer("id_data_schema")
val value = varchar("value", 1024)
override val primaryKey = PrimaryKey(uid, idCluster, idDataSchema, name = "ENTITY_PK")
}
var toBeFound = listOf(
EntityDTO(uid = UUID.fromString("4..9"), idCluster = 1, idDataSchema = 1),
EntityDTO(uid = UUID.fromString("7..3"), idCluster = 1, idDataSchema = 2),
EntityDTO(uid = UUID.fromString("6..2"), idCluster = 2, idDataSchema = 1)
)
fun selectManyEntity() : List<EntityDTO> {
val entityDTOs = transaction {
val queryResultRows = EntityTable.select {
(EntityTable.uid, EntityTable.idCluster, EntityTable.idDataSchema) // <-- every row for which the compound key combination of all three
inList
toBeFound.map {
(it.uid, it.idCluster, it.idDataSchema) // <-- has an element in 'toBeFound` list with the same compound key combination
}
}
queryResultRows.map { resultRow -> Fillers().newEntityDTO(resultRow) }.toList()
}
return entityDTOs
}
how do I have to write the query that it selects
all rows of EntityTable for which the compound primary key of (id, idCluster, idDataSchema)
is also contained in the given List supposed that every EntityDTO in the List<>
also has fields id, idCluster, idDataSchema) ???
if it helps: EntityDTO has hash() and equals() overloaded for exactly these three fields.

The only way is to make a compound expression like:
fun EntityDTO.searchExpression() = Op.build {
(EntityTable.uid eq uid) and (EntityTable.idCluster eq idCluster) and (EntityTable.idDataSchema eq idDataSchema)
}
val fullSearchExpression = toBeFound.map { it.searchExpression() }.compoundOr()
val queryResultRows = EntityTable.select(fullSearchExpression)

Related

How to do CRUD operations on multiple tables in SQLite database in Flutter?

I am building a money budgeting app where the user can split their monthly salary in 5 areas: food, social, education, travel, savings. I have a register and log in screen. This is in Flutter/dart and I'm using sqlite.
Some of the data to store:
id, username, password, email, monthly_salary, food_initial, food_final, social_initial, social_final, education_initial, education_final, travel_initial, travel_final, savings_initial, savings_final
The 'initial' refers to a value that the user will input for themselves based on the division of the monthly salary. (ie. $5000 a month/5 containers = 1000 for each container. but, the user can edit this value of 1000 as their initial budgeted money). The 'final' refers to the user's remaining money (under the assumption they don't go over) for that container.
The initial and final values get reset at the end of the month. However, I would like to save their previous month's final values so the user can see a backlog of what they did.
I initially had 2 tables for my database. One was for the USER_DATA and the other was BUDGETS table. However, after struggling to figure out how to do CRUD operations for two tables, I decided to make it all into one table. But, after experiencing trouble with one table, I want to move back to more than one table. I'm confused now on:
1. How to use CRUD operations when having more than one table on DB?
2. Do I have to build a new model class in order to have more than one table?
3. How to save some of the user's data on database, but have to wait for the rest of the information to come in (later on while they're using the app)?
When making the constructor for the model class, and then referring to the model class in the UI, it requires me to bring in all of the parameters I have, but I don't have all of those values ready yet. The user still needs to register before they can input their salary and etc. Would I have to use the Future class to get over this hurdle?
I've watched a lot of videos on others building their databases in sqlite and with Flutter but all of them usually do a simple To-Do list app where they will need to fill in every column of the table and I haven't seen one yet that includes several tables. (ie. columns: id, description, priority, date)
--
I think have a good basis now on the UI side and using TextFormFields to access the information, but connecting the database/creating it properly is confusing me.
I am also considering writing the database in a .sql file now before proceeding. I have been teaching myself Flutter through tutorials (as background knowledge).
I know this is a bit long and all over the place, but I would greatly appreciate anyone who has experience with the sqlite environment in Flutter to please help me understand how to do this correctly.
Thank you. Also, if there is any more clarification/code needed, let me know.
Model class:
import 'dart:core';
class UserData {
//user table
int _userid;
String _username;
String _password;
String _email;
String _first_name;
String _last_name;
String _date;
int _month_id;
bool _subtract; //did the user subtract money from their funds? true/false
double _month_salary;
String _log_details;
/////////////budget table
double _edu_budget_initial;
double _edu_budget_final;
double _travel_budget_initial;
double _travel_budget_final;
double _living_budget_initial;
double _living_budget_final;
double _savings_budget_initial;
double _savings_budget_final;
double _social_budget_initial;
double _social_budget_final;
// using [___] around your user's data entry makes it optional in the table
// otherwise, these are all required fields
UserData(
this._username,
this._email,
this._first_name,
this._last_name,
this._password,
this._date,
this._month_id,
this._subtract,
this._month_salary,
this._log_details,
this._edu_budget_final,
this._edu_budget_initial,
this._living_budget_final,
this._living_budget_initial,
this._savings_budget_final,
this._savings_budget_initial,
this._social_budget_final,
this._social_budget_initial,
this._travel_budget_final,
this._travel_budget_initial);
//created a Named Constructor for the ability to create multiple constructors in one class
UserData.withId(
this._userid,
this._username,
this._password,
this._first_name,
this._last_name,
this._email,
this._date,
this._month_id,
this._subtract,
this._month_salary,
this._log_details,
this._edu_budget_final,
this._edu_budget_initial,
this._living_budget_final,
this._living_budget_initial,
this._savings_budget_final,
this._savings_budget_initial,
this._social_budget_final,
this._social_budget_initial,
this._travel_budget_final,
this._travel_budget_initial);
//UserData.budgetTable(this.date, this.month_id, this.log_details, this.edu_budget_final, this.edu_budget_initial, this.living_budget_final, this.living_budget_initial,
//this.month_salary, this.savings_budget_final, this.savings_budget_initial, this.social_budget_final,
//this.social_budget_initial, this.travel_budget_final, this.travel_budget_initial);
//getters:
//userdata table
int get userid => _userid;
String get username => _username;
String get password => _password;
String get email => _email;
String get firstname => _first_name;
String get lastname => _last_name;
String get date => _date;
//budget table
int get month_id => _month_id;
bool get subtract => _subtract;
double get month_salary => _month_salary;
String get log_details => _log_details;
double get living_budget_initial => _living_budget_initial;
double get living_budget_final => _living_budget_final;
double get social_budget_initial => _social_budget_initial;
double get social_budget_final => _social_budget_final;
double get edu_budget_initial => _edu_budget_initial;
double get edu_budget_final => _edu_budget_final;
double get travel_budget_initial => _travel_budget_initial;
double get travel_budget_final => _travel_budget_final;
double get savings_budget_initial => _savings_budget_initial;
double get savings_budget_final => _savings_budget_final;
//setters:
/*
set userid(int newUserID) {
//adding condition before storing what user put in
if (newUserID <= 12) {
this._userid = newUserID;
}
}
*/
set username(String newUsername) {
if (newUsername.length <= 30) {
this._username = newUsername;
}
}
set password(String newPassword) {
if (newPassword.length <= 30)
this._password = newPassword;
}
set email(String newEmail) {
if (newEmail.length <= 50)
this._email = newEmail;
}
set first_name(String newFirstName) {
if (newFirstName.length <= 30)
this._first_name = newFirstName;
}
set last_name(String newLastName) {
if (newLastName.length <= 30)
this._last_name = newLastName;
}
set month_id(int newMonthId) {
if (newMonthId <= 12) {
this._month_id = newMonthId;
}
}
set month_salary(double newMonthSalary) {
this._month_salary = newMonthSalary;
}
set date(String newDate) {
this._date = newDate;
}
set log_details(String newLogDetails) {
if (newLogDetails.length <= 255)
this._log_details = newLogDetails;
}
set subtract(bool newSubtract) {
this._subtract = newSubtract;
}
set living_budget_initial(double newLBI) {
if (newLBI <= 10) this._living_budget_initial = newLBI;
}
set living_budget_final(double newLBF) {
if (newLBF <= 10) this._living_budget_final = newLBF;
}
set social_budget_initial(double newSOBI) {
if (newSOBI <= 10) this._social_budget_initial = newSOBI;
}
set social_budget_final(double newSOBF) {
if (newSOBF <= 10) this._social_budget_final = newSOBF;
}
set edu_budget_initial(double newEBI) {
if (newEBI <= 10) this._edu_budget_initial = newEBI;
}
set edu_budget_final(double newEBF) {
if (newEBF <= 10) this._edu_budget_final = newEBF;
}
set travel_budget_initial(double newTBI) {
if (newTBI <= 10) this._travel_budget_initial = newTBI;
}
set travel_budget_final(double newTBF) {
if (newTBF <= 10) this._travel_budget_final = newTBF;
}
set savings_budget_initial(double newSBI) {
if (newSBI <= 10) this._savings_budget_initial = newSBI;
}
set savings_budget_final(double newSBF) {
if (newSBF <= 10) this._savings_budget_final = newSBF;
}
// converting object into Map objects
Map<String, dynamic> toMap() {
var map = Map<String, dynamic>();
var userid;
if (userid != null) {
map['userid'] = _userid;
}
map['userid'] = _userid;
map['username'] = _username;
map['password'] = _password;
map['email'] = _email;
map['first_name'] = _first_name;
map['last_name'] = _last_name;
//
map['month_id'] = _month_id;
map['month_salary'] = _month_salary;
map['date'] = _date;
map['log_details'] = _log_details;
map['subtract'] = _subtract;
map['living_budget_initial'] = _living_budget_initial;
map['living_budget_final'] = _living_budget_final;
map['social_budget_initial'] = _social_budget_initial;
map['social_budget_final'] = _social_budget_final;
map['edu_budget_initial'] = _edu_budget_initial;
map['edu_budget_final'] = _edu_budget_final;
map['travel_budget_initial'] = _travel_budget_initial;
map['travel_budget_final'] = _travel_budget_final;
map['savings_budget_initial'] = _savings_budget_initial;
map['savings_budget_final'] = _savings_budget_final;
return map;
}
//converts map objects into objects
UserData.fromMapObject(Map<String, dynamic> map) {
this._userid = map['userid'];
this._username = map['username'];
this._password = map['password'];
this._email = map['email'];
this.first_name = map['first_name'];
this.last_name = map['last_name'];
//
this._month_id = map['month_id'];
this._month_salary = map['month_salary'];
this._date = map['date'];
this._log_details = map['log_details'];
this._subtract = map['subtract'];
this._living_budget_initial = map['living_budget_initial'];
this._living_budget_final = map['living_budget_final'];
this._social_budget_initial = map['social_budget_initial'];
this._social_budget_final = map['social_budget_final'];
this._edu_budget_initial = map['edu_budget_initial'];
this._edu_budget_final = map['edu_budget_final'];
this._travel_budget_initial = map['travel_budget_initial'];
this._travel_budget_final = map['travel_budget_final'];
this._savings_budget_initial = map['savings_budget_initial'];
this._savings_budget_final = map['savings_budget_final'];
}
}
Database helper class:
import 'package:sqflite/sqflite.dart';
import 'dart:async';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:jewell_1/user_data.dart';
class DbHelper {
// Create a private instance of the class
static final DbHelper _dbhelper = new DbHelper._internal();
String DB_name = "user_data.db";
static final int DATABASE_VERSION = 2;
String userDataTable = 'user_data_table';
String colUserId = 'userid';
String colUsername = 'username';
String colPassword = 'password';
String colEmail = 'email';
String colFirstName = 'first_name';
String colLastName = 'last_name';
String colDate = 'date';
String colMonthId = 'month_id';
String colSubtract = 'subtract';
String colMonthSalary = 'month_salary';
String colLogDetails = 'log_details';
String colLBI = 'living_budget_initial';
String colLBF = 'living_budget_final';
String colSOBI = 'social_budget_initial';
String colSOBF = 'social_budget_final';
String colEBI = 'edu_budget_initial';
String colEBF = 'edu_budget_final';
String colTBI = 'travel_budget_initial';
String colTBF = 'travel_budget_final';
String colSBI = 'savings_budget_initial';
String colSBF = 'savings_budget_final';
// Create an empty private named constructor
DbHelper._internal();
// Use the factory to always return the same instance
factory DbHelper() {
return _dbhelper;
}
static Database _db;
Future<Database> get db async {
if (_db == null) {
_db = await initializeDb();
}
return _db;
}
Future<Database> initializeDb() async {
Directory dir = await getApplicationDocumentsDirectory();
String path = dir.path + "todos.db";
var dbTodos = await openDatabase(path, version: DATABASE_VERSION, onCreate: _createDb);
return dbTodos;
}
void _createDb(Database db, int newVersion) async {
await db.execute(
"CREATE TABLE $userDataTable($colUserId INETEGER PRIMARY KEY, $colUsername TEXT," +
"$colPassword TEXT, $colEmail TEXT, $colFirstName TEXT,"
"$colLastName TEXT, $colDate TEXT, $colMonthId INTEGER,"
" $colSubtract BOOLEAN, $colMonthSalary DOUBLE, $colLogDetails TEXT,"
" $colEBI DOUBLE, $colEBF DOUBLE, $colTBI DOUBLE, $colTBF DOUBLE, "
" $colLBI DOUBLE, $colLBF DOUBLE, $colSBI DOUBLE, $colSBF DOUBLE,"
" $colSOBI DOUBLE, $colSOBF DOUBLE)");
}
Future<int> insertData(UserData data) async {
Database db = await this.db;
var result = await db.insert(userDataTable, data.toMap());
return result;
}
Future<List> getDatas() async {
Database db = await this.db;
var result =
await db.rawQuery("SELECT * FROM $userDataTable order by $colUserId ASC");
return result;
}
Future<int> getCount() async {
Database db = await this.db;
var result = Sqflite.firstIntValue(
await db.rawQuery("SELECT COUNT (*) FROM $userDataTable"));
return result;
}
Future<int> updateData(UserData data) async {
var db = await this.db;
var result = await db
.update(userDataTable, data.toMap(), where: "$colUserId=?", whereArgs: [data.userid]);
return result;
}
Future<int> deleteData(int id) async {
int result;
var db = await this.db;
result = await db.rawDelete("DELETE FROM $userDataTable WHERE $colUserId=$id");
return result;
}
}
To be more specific, I am confused on not writing the query of a new table, but rather at this point:
var result = await db.insert(userDataTable, data.toMap());
return result;
The result is only containing my userDataTable and if I create another table, then am I supposed to add another var result? I tried to have:
var result2 = await db.insert(budgetsTable, data.toMap());
This seemed wrong. Also, I didn't know how to return both results.
That was specifically where/when I started to scavenge the internet of people who have made CRUD operations using more than one table on sqlite with Flutter.

Chaining DBIOAction with branching, one branch does nothing

Coming from How to get max(id) of table = Rep[Option[Long]] for subsequent insert, without calling db.run in between
I have yet another problem with my code:
def add(languageCode: String,
typeId: Long,
properties: Seq[Property]): Unit = {
val dbAction = (
for{
nodeId <- (nodes.all returning nodes.all.map(_.id)) += Node(typeId)
language <- (languages.all filter (_.code === languageCode)).result.head
_ <- DBIO.seq(properties.map
{
property =>
val id = property.id
val name = property.key
val value = property.value
if(id == 0) {
val currentPropId: FixedSqlAction[Option[Long], h2Profile.api.NoStream, Effect.Read] = this.properties.all.map(_.id).max.result
val propertyId = (this.properties.all returning this.properties.all.map(_.id)) += Property(language.id.get, currentPropId + 1, name)
nodeProperties.all += NodeProperty(nodeId, 2, value)
} else {
nodeProperties.all += NodeProperty(nodeId, id, value)
}
}: _*)
} yield()).transactionally
db.run(dbAction)
}
1)
The basic idea is, that if that for each property which has id of 0, there are two inserts to be called. One to actually create that new property in some table, the other to also add the value of that property to another table.
That's in the:
if(id == 0) {
val currentPropId: Rep[Option[Long]] = this.properties.all.map(_.id).max
// FIXME get rid of hardcoded languageId
val DUMMY_LANGUAGE_ID = 1
val propertyId = (this.properties.all returning this.properties.all.map(_.id)) += Property(DUMMY_LANGUAGE_ID, currentPropId + 1, name)
nodeProperties.all += NodeProperty(nodeId, 2, value)
}
part.
Now, if that is not the case, only the value of the property needs to be inserted to the correct table, that's what the else part is for.
The problem I face is that for the case where the id == 0 nothing gets inserted.
I also tried it like this:
if(id == 0) {
val nextPropId: Rep[Option[Long]] = this.properties.all.map(_.id).max
// FIXME get rid of hardcoded languageId
val propertyId = (this.properties.all returning this.properties.all.map(_.id)) += Property(1, 10, name)
val nodeProperty = nodeProperties.all += NodeProperty(nodeId, 2, value)
propertyId andThen nodeProperty
}
but to no avail.
The else part works just fine. What am I missing here?
edit:
Because I got asked.
val propertyId = (this.properties.all returning this.properties.all.map(_.id)) += Property(language.id.get, 10, name)
val nodeProperty = nodeProperties.all += NodeProperty(nodeId, 2, value)
log.info(s"Statements: ${propertyId.statements}")
log.info(s"${nodeProperty.statements}")
gives me:
Statements: Vector(insert into "properties"
("language_id","property_id","name") values (?,?,?)) Vector(insert
into "node_property" ("node_id","property_id","value") values
(?,?,?))
Which looks just fine.
I would love to log also the content of dbAction itself but as it's a DBIOAction I did not find a way to do so in a meaningful manner.
If I set my logging to trace I get:
22:18:16:016 - [debug] s.c.QueryCompilerBenchmark - ------------------- Phase: Time ---------
22:18:16:016 - [debug] s.c.QueryCompilerBenchmark - assignUniqueSymbols: 0.456428 ms
22:18:16:016 - [debug] s.c.QueryCompilerBenchmark - inferTypes: 0.089135 ms
22:18:16:016 - [debug] s.c.QueryCompilerBenchmark - insertCompiler: 0.292443 ms
22:18:16:016 - [debug] s.c.QueryCompilerBenchmark - codeGen: 0.534494 ms
22:18:16:016 - [debug] s.c.QueryCompilerBenchmark - TOTAL: 1.372500 ms
22:18:16:016 - [debug] s.b.B.action - #5: Rollback
With no idea why the last line says Rollback as everything before (quite a lot more) seems okay to me.
edit2:
Looks like I finally have it with:
val dbAction = for {
nodeId <- (nodes.all returning nodes.all.map(_.id)) += Node(typeId)
languageId <- (languages.all filter (_.code === languageCode)).map(_.id).result.head
_ <- DBIO.seq(properties.values.map
{
property =>
val id = property.id
val name = property.key
val value = property.value
if(id == 0) {
for {
currentPropId <- this.properties.all.map(_.id).max.result
propertyId <- (this.properties.all returning this.properties.all.map(_.id)) += Property(languageId, currentPropId.get + 1, name)
_ <- this.nodeProperties.all += NodeProperty(nodeId, propertyId, value)
} yield ()
} else {
this.nodeProperties.all += NodeProperty(nodeId, id, value)
}
}: _*)
} yield ()
It looks like the final answer is to restructure a bit:
val dbAction = for {
nodeId <- (nodes.all returning nodes.all.map(_.id)) += Node(typeId)
languageId <- (languages.all filter (_.code === languageCode)).map(_.id).result.head
_ <- DBIO.seq(properties.values.map
{
property =>
val id = property.id
val name = property.key
val value = property.value
if(id == 0) {
for {
currentPropId <- this.properties.all.map(_.id).max.result
propertyId <- (this.properties.all returning this.properties.all.map(_.id)) += Property(languageId, currentPropId.get + 1, name)
_ <- this.nodeProperties.all += NodeProperty(nodeId, propertyId, value)
} yield ()
} else {
this.nodeProperties.all += NodeProperty(nodeId, id, value)
}
}: _*)
} yield ()
I would love to see other solutions, but that's the one I found after another long trial and error session. I am still unsure why I couldn't get the andThen approach to work.
And I am wondering what happens to the transactionally if something happens in the inner for comprehension. I guess it wouldn't work according to my interpretation of documentation - http://slick.lightbend.com/doc/3.2.1/dbio.html#transactions-and-pinned-sessions
Nested transactionally actions simply execute inside the existing transaction without additional savepoints.

How to sort array of custom objects by customised status value in swift 3?

lets say we have a custom class named orderFile and this class contains three properties.
class orderFile {
var name = String()
var id = Int()
var status = String()
}
a lot of them stored into an array
var aOrders : Array = []
var aOrder = orderFile()
aOrder.name = "Order 1"
aOrder.id = 101
aOrder.status = "closed"
aOrders.append(aOrder)
var aOrder = orderFile()
aOrder.name = "Order 2"
aOrder.id = 101
aOrder.status = "open"
aOrders.append(aOrder)
var aOrder = orderFile()
aOrder.name = "Order 2"
aOrder.id = 101
aOrder.status = "cancelled"
aOrders.append(aOrder)
var aOrder = orderFile()
aOrder.name = "Order 2"
aOrder.id = 101
aOrder.status = "confirmed"
aOrders.append(aOrder)
Question is: How will I sort them based on status according to open, confirm, close and cancelled?
You have to provide a value that will yield the appropriate ordering when compared in the sort function.
For example:
extension orderFile
{
var statusSortOrder: Int
{ return ["open","confirmed","closed","cancelled"].index(of: status) ?? 0 }
}
let sortedOrders = aOrders.sorted{$0.statusSortOrder < $1. statusSortOrder}
In your code you should make an array to store each aOrder with aOrders.append(aOrder) at each aOrder defination.
Then sort it with below code, refor this for more.
aOrders.sorted({ $0.status > $1.status })
The answer for swift 3 is as following
Ascending:
aOrders = aOrders.sorted(by:
{(first: orderFile, second: orderFile) -> Bool in
first.status > second.status
}
)
Descending:
aOrders = aOrders.sorted(by:
{(first: orderFile, second: orderFile) -> Bool in
first.status < second.status
}
)

Slick 3 join query one to many relationship

Imagine the following relation
One book consists of many chapters, a chapter belongs to exactly one book. Classical one to many relation.
I modeled it as this:
case class Book(id: Option[Long] = None, order: Long, val title: String)
class Books(tag: Tag) extends Table[Book](tag, "books")
{
def id = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)
def order = column[Long]("order")
def title = column[String]("title")
def * = (id, order, title) <> (Book.tupled, Book.unapply)
def uniqueOrder = index("order", order, unique = true)
def chapters: Query[Chapters, Chapter, Seq] = Chapters.all.filter(_.bookID === id)
}
object Books
{
lazy val all = TableQuery[Books]
val findById = Compiled {id: Rep[Long] => all.filter(_.id === id)}
def add(order: Long, title: String) = all += new Book(None, order, title)
def delete(id: Long) = all.filter(_.id === id).delete
// def withChapters(q: Query[Books, Book, Seq]) = q.join(Chapters.all).on(_.id === _.bookID)
val withChapters = for
{
(Books, Chapters) <- all join Chapters.all on (_.id === _.bookID)
} yield(Books, Chapters)
}
case class Chapter(id: Option[Long] = None, bookID: Long, order: Long, val title: String)
class Chapters(tag: Tag) extends Table[Chapter](tag, "chapters")
{
def id = column[Option[Long]]("id", O.PrimaryKey, O.AutoInc)
def bookID = column[Long]("book_id")
def order = column[Long]("order")
def title = column[String]("title")
def * = (id, bookID, order, title) <> (Chapter.tupled, Chapter.unapply)
def uniqueOrder = index("order", order, unique = true)
def bookFK = foreignKey("book_fk", bookID, Books.all)(_.id.get, onUpdate = ForeignKeyAction.Cascade, onDelete = ForeignKeyAction.Restrict)
}
object Chapters
{
lazy val all = TableQuery[Chapters]
val findById = Compiled {id: Rep[Long] => all.filter(_.id === id)}
def add(bookId: Long, order: Long, title: String) = all += new Chapter(None, bookId, order, title)
def delete(id: Long) = all.filter(_.id === id).delete
}
Now what I want to do:
I want to query all or a specific book (by id) with all their chapters
Translated to plain SQL, something like:
SELECT * FROM books b JOIN chapters c ON books.id == c.book_id WHERE books.id = 10
but in Slick I can't really get this whole thing to work.
What I tried:
object Books
{
//...
def withChapters(q: Query[Books, Book, Seq]) = q.join(Chapters.all).on(_.id === _.bookID)
}
as well as:
object Books
{
//...
val withChapters = for
{
(Books, Chapters) <- all join Chapters.all on (_.id === _.bookID)
} yield(Books, Chapters)
}
but to no avail. (I use ScalaTest and I get an empty result (for def withChapters(...)) or another exception for the val withChapters = for...)
How to go on about this? I tried to keep to the documentation, but I'm doing something wrong obviously.
Also: Is there an easy way to see the actual query as a String? I only found query.selectStatement and the like, but that's not available for my joined query. Would be great for debugging to see if the actual query was wrong.
edit: My test looks like this:
class BookWithChapters extends FlatSpec with Matchers with ScalaFutures with BeforeAndAfter
{
val db = Database.forConfig("db.test.h2")
private val books = Books.all
private val chapters = Chapters.all
before { db.run(setup) }
after {db.run(tearDown)}
val setup = DBIO.seq(
(books.schema).create,
(chapters.schema).create
)
val tearDown = DBIO.seq(
(books.schema).drop,
(chapters.schema).drop
)
"Books" should "consist of chapters" in
{
db.run(
DBIO.seq
(
Books.add(0, "Book #1"),
Chapters.add(0, 0, "Chapter #1")
)
)
//whenReady(db.run(Books.withChapters(books).result)) {
whenReady(db.run(Books.withChapters(1).result)) {
result => {
// result should have length 1
print(result(0)._1)
}
}
}
}
like this I get an IndexOutOfBoundsException.
I used this as my method:
object Books
{
def withChapters(id: Long) = Books.all.filter(_.id === id) join Chapters.all on (_.id === _.bookID)
}
also:
logback.xml looks like this:
<configuration>
<logger name="slick.jdbc.JdbcBackend.statement" level="DEBUG/>
</configuration>
Where can I see the logs? Or what else do I have to do to see them?
To translate your query...
SELECT * FROM books b JOIN chapters c ON books.id == c.book_id WHERE books.id = 10
...to Slick we can filter the books:
val bookTenChapters =
Books.all.filter(_.id === 10L) join Chapters.all on (_.id === _.bookID)
This will give you a query that returns Seq[(Books, Chapters)]. If you want to select different books, you can use a different filter expression.
Alternatively, you may prefer to filter on the join:
val everything =
Books.all join Chapters.all on (_.id === _.bookID)
val bookTenChapters =
everything.filter { case (book, chapter) => book.id === 10L }
That will probably be closer to your join. Check the SQL generated with the database you use to see which you prefer.
You can log the query by creating a src/main/resources/logback.xml file and set:
<logger name="slick.jdbc.JdbcBackend.statement" level="DEBUG"/>
I have an example project with logging set up. You will need to change INFO to DEBUG in the xml file in, e.g., the chapter-01 folder.

LINQ orderby int array index value

Using LINQ I would like to sort by the passed in int arrays index.
So in the code below attribueIds is my int array. I'm using the integers in that array for the where clause but I would like the results in the order that they were in while in the array.
public List BuildTable(int[] attributeIds)
{
using (var dc = new MyDC())
{
var ordering = attributeIds.ToList();
var query = from att in dc.DC.Ecs_TblAttributes
where attributeIds.Contains(att.ID)
orderby(ordering.IndexOf(att.ID))
select new Common.Models.Attribute
{
AttributeId = att.ID,
DisplayName = att.DisplayName,
AttributeName = att.Name
};
return query.ToList();
}
}
I would recommend selecting from the attributeIDs array instead. This will ensure that your items will be correctly ordered without requiring a sort.
The code should go something like this:
var query =
from id in attributeIds
let att = dc.DC.Ecs_TblAttributes.FirstOrDefault(a => a.ID == id)
where att != null
select new Common.Models.Attribute
{
AttributeId = att.ID,
DisplayName = att.DisplayName,
AttributeName = att.Name
};
Why don't you join:
public List BuildTable(int[] attributeIds)
{
using (var dc = new MyDC())
{
var query = from attID in attributeIds
join att in dc.DC.Ecs_TblAttributes
on attID equals att.ID
select new Common.Models.Attribute
{
AttributeId = attID,
DisplayName = att.DisplayName,
AttributeName = att.Name
};
return query.ToList();
}
}

Resources