https://goo.gl/aYQnfh Alec Strong & Jake Wharton The Resurgence of SQL
A presentation at Droidcon NYC in September 2017 in New York, NY, USA by Jake Wharton
 
                https://goo.gl/aYQnfh Alec Strong & Jake Wharton The Resurgence of SQL
 
                Why Persistence?
 
                Why Persistence?
 
                Why Persistence? https://goo.gl/1rjsN9
 
                Why Persistence? https://goo.gl/1rjsN9
 
                Why Persistence?
 
                Flat Files
 
                Flat Files sharedPreferences.edit () .putString( "title" , "Shrek" ) .putInt( "year" , 2001 ) .putFloat( "rating" , 10.0f ) .apply()
 
                Flat Files sharedPreferences.edit () .putString( "title" , "Shrek" ) .putInt( "year" , 2001 ) .putFloat( "rating" , 10.0f ) .apply() < map
< string name= "title"
Shrek</ string
< int name= "year" value= "2001" /> < float name= "rating" value= "10.0" /> </ map
 
                Flat Files sharedPreferences.edit () .putString( "title" , "Shrek" ) .putInt( "year" , 2001 ) .putFloat( "rating" , 10.0f ) .apply()
sharedPreferences.edit () .putInt( "volume" , 8 ) .apply() < map
< string name= "title"
Shrek</ string
< int name= "year" value= "2001" /> < float name= "rating" value= "10.0" /> </ map
 
                Flat Files sharedPreferences.edit () .putString( "title" , "Shrek" ) .putInt( "year" , 2001 ) .putFloat( "rating" , 10.0f ) .apply()
sharedPreferences.edit () .putInt( "volume" , 8 ) .apply() < map
< string name= "title"
Shrek</ string
< int name= "year" value= "2001" /> < float name= "rating" value= "10.0" />
< int name= "volume" value= "8" /> </ map
 
                Flat Files sharedPreferences.edit () .putString( "title" , "Shrek" ) .putInt( "year" , 2001 ) .putFloat( "rating" , 10.0f ) .apply()
sharedPreferences.edit () .putInt( "volume" , 8 ) .apply() < map
< string name= "title"
Shrek</ string
< int name= "year" value= "2001" /> < float name= "rating" value= "10.0" />
< int name= "volume" value= "8" /> </ map
 
                Flat Files val sharedPreferences =
context.getSharedPreferences(
"user123" , MODE_PRIVATE )
sharedPreferences.edit () .putString( "title" , "Shrek" ) .putInt( "year" , 2001 ) .putFloat( "rating" , 10.0f ) .apply()
sharedPreferences.edit () .putInt( "volume" , 8 ) .apply() < map
< string name= "title"
Shrek</ string
< int name= "year" value= "2001" /> < float name= "rating" value= "10.0" />
< int name= "volume" value= "8" /> </ map
 
                Flat Files val sharedPreferences =
context.getSharedPreferences(
"user123" , MODE_PRIVATE )
sharedPreferences.edit () .putString( "title" , "Shrek" ) .putInt( "year" , 2001 ) .putFloat( "rating" , 10.0f ) .apply()
sharedPreferences.edit () .putInt( "volume" , 8 ) .apply() < map
< string name= "title"
Shrek</ string
< int name= "year" value= "2001" /> < float name= "rating" value= "10.0" />
< int name= "volume" value= "8" /> </ map
 
                Flat Files val sharedPreferences =
context.getSharedPreferences(
"user123" , MODE_PRIVATE )
sharedPreferences.edit () .putString( "title" , "Shrek" ) .putInt( "year" , 2001 ) .putFloat( "rating" , 10.0f ) .apply()
sharedPreferences.edit () .putInt( "volume" , 8 ) .apply() < map
< string name= "title"
Shrek</ string
< int name= "year" value= "2001" /> < float name= "rating" value= "10.0" />
< int name= "volume" value= "8" /> </ map
 
                Flat Files data class User(
val name : String,
val age : Int,
val email : String )
 
                Flat Files data class User(
val name : String,
val age : Int,
val email : String )
val bob = User( "Bob" , 20 , "bob@bob.bob" ) buffer(sink(file)). use {
adapter.toJson( it , bob ) }
 
                Flat Files data class User(
val name : String,
val age : Int,
val email : String )
val bob = User( "Bob" , 20 , "bob@bob.bob" ) buffer(sink(file)). use {
adapter.toJson( it , bob ) }
{ "name" : "Bob" , "age" : 20 , "email" : "bob@bob.bob" }
 
                Flat Files data class User(
val name : String,
val age : Int,
val email : String ) X
val bob = User( "Bob" , 20 , "bob@bob.bob" ) buffer(sink(file)). use {
adapter.toJson( it , bob ) } Y
{ "name" : "Bob" , "age" : 20 , "email" : "bob@bob.bob" } Z
 
                Flat Files data class User(
val name : String,
val age : Int,
val email : String,
val friends : List<User> = emptyList()
) X
val alice = User( "Alice" , 20 , "alice@alice.alice" )
val bob = User( "Bob" , 20 , "bob@bob.bob" , alice)
buffer(sink(file)). use {
adapter.toJson( it , bob ) } Y
{ "name" : "Bob" , "age" : 20 , "email" : "bob@bob.bob" , "friends" :[{ "name" : "Alice" , "age" : 20 , "email" : "alice@alice.alice" }]} Z
 
                data class User(
val name : String,
val age : Int,
val email : String,
val friends : List<User> = emptyList()
) X
val alice = User( "Alice" , 20 , "alice@alice.alice" )
val bob = User( "Bob" , 20 , "bob@bob.bob" , alice)
buffer(sink(file)). use {
adapter.toJson( it , bob ) } Y
{ “name" : "Bob" , "age" : 20 , "email" : "bob@bob.bob" , "friends" :[{ "name" : "Alice" , “age" : 20 , "email" : "alice@alice.alice" }]} Z
Flat Files
 
                data class User(
val name : String,
val age : Int,
val email : String,
val friends : List<User> = emptyList()
) X
val alice = User( "Alice" , 20 , "alice@alice.alice" )
val bob = User( "Bob" , 20 , "bob@bob.bob" , listOf(alice))
buffer(sink(file)). use {
adapter.toJson( it , bob ) } Y
{ “name" : "Bob" , "age" : 20 , "email" : "bob@bob.bob" , "friends" :[{ "name" : "Alice" , “age" : 20 , "email" : "alice@alice.alice" }]} Z
Flat Files
 
                Flat Files data class User(
val name : String,
val age : Int,
val email : String,
val friends : List<User> = emptyList()
) X
val alice = User( "Alice" , 20 , "alice@alice.alice" )
val bob = User( "Bob" , 20 , "bob@bob.bob" , listOf(alice))
buffer(sink(file)). use {
adapter.toJson( it , bob ) } Y
{ “name” : ”Bob" , "age" : 20 , "email" : "bob@bob.bob" , "friends" :[{ "name" : "Alice" , “age" : 20 , "email" : "alice@alice.alice" }]} Z
 
                data class User(
val name : String,
val age : Int,
val email : String,
val friends : List<User> = emptyList()
) X
val alice = User( "Alice" , 20 , "alice@alice.alice" )
val bob = User( "Bob" , 20 , "bob@bob.bob" , listOf(alice))
buffer(sink(file)). use {
adapter.toJson( it , bob ) } Y
{ “name” : ”Bob" , "age" : 20 , "email" : "bob@bob.bob" , "friends" :[{ "name" : "Alice" , “age" : 20 , "email" : "alice@alice.alice" }]} ZZ
Flat Files
 
                Object DBs data class User(
val name : String,
val age : Int,
val email : String ,
val friends : List<User> = emptyList()
)
 
                data class User(
val name : String,
val age : Int,
val email : String ,
val friends : List<User> = emptyList()
) : MagicObject() Object DBs
 
                data class User(
var name : String,
var age : Int,
var email : String ,
var friends : List<User> = emptyList()
) : MagicObject() Object DBs
 
                Object DBs data class User(
var name : String,
var age : Int,
var email : String ,
var friends : List<User> = emptyList()
) : MagicObject()
 
                Object DBs data class User(
var name : String,
var age : Int,
var email : String ,
var friends : List<User> = emptyList()
) : MagicObject() .observeOn(mainThread())
 
                Object DBs data class User(
var name : String,
var age : Int,
var email : String ,
var friends : List<User> = emptyList()
) : MagicObject() .observeOn(mainThread())
 
                Object DBs data class User(
var name : String,
var age : Int,
var email : String ,
var friends : List<User> = emptyList()
) : MagicObject() // Users who are 20 or older:
db.where(User:: class.java ).greaterThanEqualTo( "age" , 20 ).findList()
 
                data class User(
var name : String,
var age : Int,
var email : String ,
var friends : List<User> = emptyList()
) : MagicObject() // Users who are 20 or older:
db.where(User:: class.java ).greaterThanEqualTo( "age" , 20 ).findList() Object DBs
 
                Object DBs data class User(
var name : String,
var age : Int,
var email : String ,
var friends : List<User> = emptyList()
) : MagicObject() // Users who are 20 or older:
db.where(User:: class.java ).greaterThanEqualTo( "age" , 20 ).findList()
 
                Object DBs data class User(
var name : String,
var age : Int,
var email : String ,
var friends : List<User> = emptyList()
) : MagicObject() // Users who are 20 or older:
db.where(User:: class.java ).greaterThanEqualTo( "age" , 20 ).findList() // Users with 3 or more friends: // Query all users, count and filter in code :(
 
                Object DBs data class User(
var name : String,
var age : Int,
var email : String ,
var friends : List<User> = emptyList()
) : MagicObject() // Users who are 20 or older:
db.where(User:: class.java ).greaterThanEqualTo( "age" , 20 ).findList() // Users with 3 or more friends: // Query all users, count and filter in code :(
// Users friends by Bob (transitively) // Query friends, friends of friends and combine in code :(
 
                data class User(
var name : String,
var age : Int,
var email : String ,
var friends : List<User> = emptyList()
) : MagicObject() Object DBs
 
                ORMs data class User(
val id : Long,
val name : String,
val friends : Set<User> ) X data class Checkin(
val location : String,
val time : OffsetDateTime,
val users : Set<User> ) Y
 
                ORMs @Entity data class User(
@Id @GeneratedValue ( strategy = AUTO )
val id : Long,
val name : String,
@ManyToMany
val friends : Set<User> ) X
@Entity data class Checkin(
val location : String,
val time : OffsetDateTime,
@ManyToMany
val users : Set<User> ) Y
 
                ORMs @Entity data class User(
@Id @GeneratedValue ( strategy = AUTO )
val id : Long,
val name : String,
@ManyToMany
val friends : Set<User> ) X
@Entity data class Checkin(
val location : String,
val time : OffsetDateTime,
@ManyToMany
val users : Set<User> ) Y
 
                ORMs // Find your friend’s checkins
val me = session.createCritera(User:: class.java ) .add(eq( "id" , MY_ID)).list().first() val checkins = session.createCritera(Checkin:: class.java ) .add(eq( "users.name" , me. friends ))
 
                ORMs // Find your friend’s checkins
val me = session.createCritera(User:: class.java ) .add(eq( "id" , MY_ID)).list().first() val checkins = session.createCritera(Checkin:: class.java ) .add(eq( "users.name" , me. friends ))
 
                ORMs // Find your friend’s checkins
val me = session.createCritera(User:: class.java ) .add(eq( "id" , MY_ID)).list().first() val checkins = session.createCritera(Checkin:: class.java ) .add(eq( "users.name" , me. friends )) @Entity data class User(
@Id @GeneratedValue ( strategy = AUTO )
val id : Long,
val name : String,
@ManyToMany
val friends : Set<User> ) X
 
                • Data Definition Language (DDL) SQL
 
                • Data Definition Language (DDL) • Data Manipulation Language (DML) SQL
 
                • Data Definition Language (DDL) • Data Manipulation Language (DML) • Data Control Language (DCL) • (Not a thing in SQLite) SQL
 
                CREATE TABLE user ( _id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT , name TEXT NOT NULL ); Data Definition CREATE TABLE friendship ( friend1 INTEGER NOT NULL REFERENCES user, friend2 INTEGER NOT NULL REFERENCES user, became_friends INTEGER NOT NULL DEFAULT CURRENT_TIME ,
PRIMARY KEY (friend1, friend2) );
 
                CREATE TABLE checkin ( _id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT , name TEXT NOT NULL , time INTEGER
NOT NULL ); Data Definition CREATE TABLE user_checkin ( checkin_id INTEGER NOT NULL REFERENCES checkin, user_id INTEGER NOT NULL REFERENCES user,
PRIMARY KEY (checkin_id, user_id) );
 
                fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
MY_ID }
. map { it . friend2 }
MY_ID }
. map { it . friend1 }
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
  .size()
} Data Manipulation
 
                Data Manipulation
 
                Data Manipulation
 
                fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
MY_ID }
. map { it . friend2 }
MY_ID }
. map { it . friend1 }
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
  .size()
} Data Manipulation
 
                fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
MY_ID }
. map { it . friend2 }
MY_ID }
. map { it . friend1 }
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
  .size()
} X Data Manipulation
friendship
 
                fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
MY_ID }
. map { it . friend2 }
MY_ID }
. map { it . friend1 }
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
  .size()
} X Data Manipulation
friendship FROM
 
                fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
MY_ID }
. map { it . friend2 }
MY_ID }
. map { it . friend1 }
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
  .size()
} X Data Manipulation FROM friendship
MY_ID
 
                fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
MY_ID }
. map { it . friend2 }
MY_ID }
. map { it . friend1 }
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
  .size()
} X Data Manipulation FROM friendship
friend1
= MY_ID WHERE
 
                fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
MY_ID }
. map { it . friend2 }
MY_ID }
. map { it . friend1 }
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
  .size()
} X Data Manipulation FROM friendship
friend1
= :my_id WHERE
 
                fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
MY_ID }
. map { it . friend2 }
MY_ID }
. map { it . friend1 }
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
  .size()
} X Data Manipulation FROM friendship
WHERE friend1
= :my_id
friend2
 
                fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
MY_ID }
. map { it . friend2 }
MY_ID }
. map { it . friend1 }
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
  .size()
} X Data Manipulation
friend2 FROM friendship
WHERE friend1
= :my_id SELECT
 
                fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
MY_ID }
. map { it . friend2 }
MY_ID }
. map { it . friend1 }
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
  .size()
} X Data Manipulation SELECT friend2 FROM friendship
WHERE friend1
= :my_id
 
                fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
MY_ID }
. map { it . friend2 }
MY_ID }
. map { it . friend1 }
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
  .size()
} X Data Manipulation SELECT friend2 FROM friendship
WHERE friend1
= :my_id
friendship
               
MY_ID
            friend1
                       
:my_id
 
                fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
MY_ID }
. map { it . friend2 }
MY_ID }
. map { it . friend1 }
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
  .size()
} X Data Manipulation SELECT friend2 FROM friendship
WHERE friend1
= :my_id
friendship FROM
MY_ID
            friend1
                       
:my_id
 
                fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
MY_ID }
. map { it . friend2 }
MY_ID }
. map { it . friend1 }
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
  .size()
} X Data Manipulation SELECT friend2 FROM friendship
WHERE friend1
= :my_id FROM friendship
  friend2
= :my_id WHERE friend1
MY_ID
 
                fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
MY_ID }
. map { it . friend2 }
MY_ID }
. map { it . friend1 }
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
  .size()
} X Data Manipulation SELECT friend2 FROM friendship
WHERE friend1
= :my_id friend1 FROM friendship
WHERE friend2
= :my_id SELECT
 
                fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
MY_ID }
. map { it . friend2 }
MY_ID }
. map { it . friend1 }
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
  .size()
} X Data Manipulation SELECT friend2 FROM friendship
WHERE friend1
= :my_id SELECT
friend1 FROM friendship
WHERE friend2
= :my_id
 
                fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
MY_ID }
. map { it . friend2 }
MY_ID }
. map { it . friend1 }
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
  .size()
} X Data Manipulation SELECT friend2 FROM friendship
WHERE friend1
= :my_id +
SELECT
friend1 FROM friendship
WHERE friend2
= :my_id
 
                fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
MY_ID }
. map { it . friend2 }
MY_ID }
. map { it . friend1 }
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
  .size()
} X Data Manipulation SELECT friend2 FROM friendship
WHERE friend1
= :my_id UNION
SELECT
friend1 FROM friendship
WHERE friend2
= :my_id
 
                my_id
my_id
fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
MY_ID }
. map { it . friend2 }
MY_ID }
. map { it . friend1 }
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
.size()
} X SELECT friend2
FROM friendship WHERE friend1 = :my_id UNION SELECT friend1
FROM friendship WHERE friend2 = :my_id Data Manipulation
 
                my_id
my_id
fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
.size()
} X SELECT friend2
FROM friendship WHERE friend1 = :my_id UNION SELECT friend1
FROM friendship WHERE friend2 = :my_id Data Manipulation
 
                my_id
my_id
fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friendships: Collection<Friendship> 
): Long {
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
.size()
} X SELECT friend2
FROM friendship WHERE friend1 = :my_id UNION SELECT friend1
FROM friendship WHERE friend2 = :my_id Data Manipulation
 
                my_id
my_id
fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friends: Collection<Long
): Long {
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
.size()
} X SELECT friend2
FROM friendship WHERE friend1 = :my_id UNION SELECT friend1
FROM friendship WHERE friend2 = :my_id Data Manipulation
Friendship
 
                my_id
my_id
fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friends: Collection<Long
): Long {
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
.size()
} X SELECT friend2
FROM friendship WHERE friend1 = :my_id UNION SELECT friend1
FROM friendship WHERE friend2 = :my_id Data Manipulation
 
                Data Manipulation
 
                my_id
my_id
fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friends: Collection<Long
): Long {
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
.size()
} X SELECT friend2
FROM friendship WHERE friend1 = :my_id UNION SELECT friend1
FROM friendship WHERE friend2 = :my_id Data Manipulation
 
                my_id
my_id
fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friends: Collection<Long
): Long {
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
.size()
} X Data Manipulation
user_ checkin SELECT friend2
FROM friendship WHERE friend1 = :my_id UNION SELECT friend1
FROM friendship WHERE friend2 = :my_id
 
                my_id
my_id
fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friends: Collection<Long
): Long {
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
.size()
} X Data Manipulation FROM user_checkin SELECT friend2
FROM friendship WHERE friend1 = :my_id UNION SELECT friend1
FROM friendship WHERE friend2 = :my_id
 
                my_id
my_id
fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friends: Collection<Long
): Long {
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
.size()
} X Data Manipulation FROM user_checkin SELECT friend2
FROM friendship WHERE friend1 = :my_id UNION SELECT friend1
FROM friendship WHERE friend2 = :my_id
IN
user_id in friends
 
                my_id
my_id
fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friends: Collection<Long
): Long {
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
.size()
} X Data Manipulation FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION
SELECT friend1
FROM friendship
WHERE friend2 = :my_id
) Y in f
r
i
e
n
d
s
 
                my_id
my_id
fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friends: Collection<Long
): Long {
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
.size()
} X Data Manipulation FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION
SELECT friend1
FROM friendship
WHERE friend2 = :my_id
) Y in
checkin_id
 
                my_id
my_id
fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friends: Collection<Long
): Long {
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
.size()
} X Data Manipulation checkin_id
FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION
SELECT friend1
FROM friendship
WHERE friend2 = :my_id
) Y in SELECT
 
                my_id
my_id
fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friends: Collection<Long
): Long {
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
.size()
} X Data Manipulation SELECT checkin_id
FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION
SELECT friend1
FROM friendship
WHERE friend2 = :my_id
) Y in
distinct
DISTINCTP
 
                my_id
my_id
fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friends: Collection<Long
): Long {
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
.size()
} X Data Manipulation SELECT DISTINCT P checkin_id
FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION
SELECT friend1
FROM friendship
WHERE friend2 = :my_id
) Y in
distinct
 
                my_id
my_id
fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friends: Collection<Long
): Long {
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
.size()
} X Data Manipulation SELECT DISTINCT P checkin_id
FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION
SELECT friend1
FROM friendship
WHERE friend2 = :my_id
) Y in size
count
 
                my_id
my_id
fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friends: Collection<Long
): Long {
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
.size()
} X Data Manipulation SELECT
count( DISTINCT P checkin_id ) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION
SELECT friend1
FROM friendship
WHERE friend2 = :my_id
) Y in size
 
                my_id
my_id
fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friends: Collection<Long
): Long {
return checkins . filter { it . user_id in friends }
. map { it . checkin_id }
. distinct ()
.size()
} X Data Manipulation SELECT
count( DISTINCT P checkin_id ) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION
SELECT friend1
FROM friendship
WHERE friend2 = :my_id
) Y in size
 
                fun friendsCheckins(
checkins: Collection<UserCheckin>, 
friends: Collection<Long
): Long {
} X Data Manipulation SELECT
count( DISTINCT P checkin_id ) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION
SELECT friend1
FROM friendship
WHERE friend2 = :my_id
) Y in size
 
                Data Manipulation in SQL! SELECT count( DISTINCT P checkin_id ) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION
SELECT friend1
FROM friendship
WHERE friend2 = :my_id
) Y in size
 
                SQL
 
                SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION SELECT friend1
FROM friendship
WHERE friend2 = :my_id ) Y SQL
 
                SELECT count( DISTINCT checkin_ id) FROM user_c heckin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION SELECT friend1
FROM friendship
WHERE friend2 = :my_id JOIN friendship ON ( (user_id = friend1 AND friend2 = :my_id) OR
(user_id = friend2 AND friend1 = :my_id) ) Y SQL
 
                SELECT count( DISTINCT checkin_id) FROM user_checkin JOIN friendship ON ( (user_id = friend1 AND friend2 = :my_id) OR
(user_id = friend2 AND friend1 = :my_id) ) Y SQL
 
                SQL
 
                • Save DB file to external storage and pull with adb • Stetho Debugging
 
                • Save DB file to external storage and pull with adb • Stetho • View DB file with a SQLite browser (sqlitebrowser) Debugging
 
                • Save DB file to external storage and pull with adb • Stetho • View DB file with a SQLite browser (sqlitebrowser) • Run queries to learn more Debugging
 
                • Save DB file to external storage and pull with adb • Stetho • View DB file with a SQLite browser (sqlitebrowser) • Run queries to learn more • EXPLAIN QUERY PLAN Debugging
 
                SELECT count( DISTINCT checkin_id) FROM user_checkin JOIN friendship ON ( (user_id = friend1 AND friend2 = :my_id) OR
(user_id = friend2 AND friend1 = :my_id) ) Y EXPLAIN QUERY PLAN
 
                EXPLAIN QUERY PLAN SELECT count( DISTINCT checkin_id) FROM user_checkin JOIN friendship ON ( (user_id = friend1 AND friend2 = :my_id) OR
(user_id = friend2 AND friend1 = :my_id) ) Y EXPLAIN QUERY PLAN
 
                EXPLAIN QUERY PLAN SELECT count( DISTINCT checkin_id) FROM user_checkin JOIN friendship ON ( (user_id = friend1 AND friend2 = :my_id) OR
(user_id = friend2 AND friend1 = :my_id) ) Y selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "1" "1" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)" "0" "1" "1" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)" EXPLAIN QUERY PLAN
 
                EXPLAIN QUERY PLAN SELECT count( DISTINCT checkin_id) FROM user_checkin
JOIN friendship ON (
(user_id = friend1 AND friend2 = :my_id)
OR
(user_id = friend2 AND friend1 = :my_id)
) selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "1" "1" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)" "0" "1" "1" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)" Where is this instruction in the FROM clause
 
                selectid order from detail "0" "0" "0" " SCAN TABLE user_checkin" "0" "1" "1" " SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)" "0" "1" "1" " SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)" EXPLAIN QUERY PLAN SELECT count( DISTINCT checkin_id) FROM user_checkin
JOIN friendship ON (
(user_id = friend1 AND friend2 = :my_id)
OR
(user_id = friend2 AND friend1 = :my_id)
) Full table scan
 
                selectid order from detail "0" "0" "0" " SCAN TABLE user_checkin" "0" "1" "1" " SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)" "0" "1" "1" " SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)" EXPLAIN QUERY PLAN SELECT count( DISTINCT checkin_id) FROM user_checkin
JOIN friendship ON (
(user_id = friend1 AND friend2 = :my_id)
OR
(user_id = friend2 AND friend1 = :my_id)
) Full table scan Search a subset using an index
 
                EXPLAIN QUERY PLAN SELECT count( DISTINCT checkin_id) FROM user_checkin JOIN friendship ON (
(user_id = friend1 AND friend2 = :my_id)
OR
(user_id = friend2 AND friend1 = :my_id)
) selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "1" "1" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)" "0" "1" "1" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)" The nesting order of this instruction
 
                EXPLAIN QUERY PLAN SELECT count( DISTINCT checkin_id) FROM user_checkin JOIN friendship ON ( (user_id = friend1 AND friend2 = :my_id) OR
(user_id = friend2 AND friend1 = :my_id) ) Y selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "1" "1" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)" "0" "1" "1" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)"
 
                EXPLAIN QUERY PLAN SELECT count( DISTINCT checkin_id) FROM user_checkin JOIN friendship ON ( (user_id = friend1 AND friend2 = :my_id) OR
(user_id = friend2 AND friend1 = :my_id) ) selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "1" "1" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)" "0" "1" "1" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)"
 
                EXPLAIN QUERY PLAN SELECT count( DISTINCT checkin_id) FROM user_checkin JOIN friendship ON ( (user_id = friend1 AND friend2 = :my_id) OR
(user_id = friend2 AND friend1 = :my_id) ) selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "1" "1" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)" "0" "1" "1" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)"
 
                EXPLAIN QUERY PLAN SELECT count( DISTINCT checkin_id) FROM user_checkin JOIN friendship ON ( (user_id = friend1 AND friend2 = :my_id) OR
(user_id = friend2 AND friend1 = :my_id) ) Y selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "1" "1" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)" "0" "1" "1" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)"
 
                EXPLAIN QUERY PLAN SELECT count( DISTINCT checkin_id) FROM user_checkin JOIN friendship ON ( (user_id = friend1 AND friend2 = :my_id) OR
(user_id = friend2 AND friend1 = :my_id) ) Y selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "1" "1" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)" "0" "1" "1" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=? AND friend2=?)" EXPLAIN QUERY PLAN
 
                EXPLAIN QUERY PLAN SELECT count( DISTINCT checkin_id) FROM user_checkin JOIN friendship ON ( (user_id = friend1 AND friend2 = :my_id) OR
(user_id = friend2 AND friend1 = :my_id) ) Y EXPLAIN QUERY PLAN
 
                SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION SELECT friend1
FROM friendship
WHERE friend2 = :my_id ) Y EXPLAIN QUERY PLAN
 
                SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION SELECT friend1
FROM friendship
WHERE friend2 = :my_id ) Y selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "0" "0" "EXECUTE LIST SUBQUERY 1" "2" "0" "0" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=?)" "3" "0" "0" "SCAN TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1" "1" "0" "0" "COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)" EXPLAIN QUERY PLAN
 
                selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "0" "0" "EXECUTE LIST SUBQUERY 1" "2" "0" "0" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=?)" "3" "0" "0" "SCAN TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1" "1" "0" "0" "COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)" SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION SELECT friend1
FROM friendship
WHERE friend2 = :my_id ) The subquery ID for the instruction
 
                selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "0" "0" "EXECUTE LIST SUBQUERY 1" "2" "0" "0" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=?)" "3" "0" "0" "SCAN TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1" "1" "0" "0" "COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)" SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION SELECT friend1
FROM friendship
WHERE friend2 = :my_id ) Y
 
                selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "0" "0" "EXECUTE LIST SUBQUERY 1" "2" "0" "0" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=?)" "3" "0" "0" "SCAN TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1" "1" "0" "0" "COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)" SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION SELECT friend1
FROM friendship
WHERE friend2 = :my_id ) Y Can either be EXECUTE or EXECUTE CORRELATED
 
                SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION SELECT friend1
FROM friendship
WHERE friend2 = :my_id ) Y selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "0" "0" "EXECUTE LIST SUBQUERY 1" "2" "0" "0" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=?)" "3" "0" "0" "SCAN TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1" "1" "0" "0" "COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)"
 
                SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION SELECT friend1
FROM friendship
WHERE friend2 = :my_id ) Y selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "0" "0" "EXECUTE LIST SUBQUERY 1" "2" "0" "0" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=?)" "3" "0" "0" "SCAN TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1" "1" "0" "0" "COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)"
 
                SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION SELECT friend1
FROM friendship
WHERE friend2 = :my_id ) Y selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "0" "0" "EXECUTE LIST SUBQUERY 1" "2" "0" "0" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=?)" "3" "0" "0" "SCAN TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1" "1" "0" "0" "COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)"
 
                SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION SELECT friend1
FROM friendship
WHERE friend2 = :my_id ) Y selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "0" "0" "EXECUTE LIST SUBQUERY 1" "2" "0" "0" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=?)" "3" "0" "0" "SCAN TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1" "1" "0" "0" "COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)"
 
                selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "0" "0" "EXECUTE LIST SUBQUERY 1" "2" "0" "0" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=?)" "3" "0" "0" "SCAN TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1" "1" "0" "0" "COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)" SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION SELECT friend1
FROM friendship
WHERE friend2 = :my_id ) Y
 
                SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION SELECT friend1
FROM friendship
WHERE friend2 = :my_id ) Y selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "0" "0" "EXECUTE LIST SUBQUERY 1" "2" "0" "0" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=?)" "3" "0" "0" "SCAN TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1" "1" "0" "0" "COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)"
 
                SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION SELECT friend1
FROM friendship
WHERE friend2 = :my_id ) Y selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "0" "0" "EXECUTE LIST SUBQUERY 1" "2" "0" "0" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=?)" "3" "0" "0" "SCAN TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1" "1" "0" "0" "COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)" EXPLAIN QUERY PLAN
 
                SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION SELECT friend1
FROM friendship
WHERE friend2 = :my_id ) Y EXPLAIN QUERY PLAN
 
                • Subquery is stored, not correlated EXPLAIN QUERY PLAN
 
                • Subquery is stored, not correlated • No order of depth, there is only a single scan through the checkin table EXPLAIN QUERY PLAN
 
                • Subquery is stored, not correlated • No order of depth, there is only a single scan through the checkin table • Scans could be searches if we created an index manually EXPLAIN QUERY PLAN
 
                SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION SELECT friend1
FROM friendship
WHERE friend2 = :my_id ) Y EXPLAIN QUERY PLAN
 
                SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION SELECT friend1
FROM friendship
WHERE friend2 = :my_id ) Y selectid order from detail "0" "0" "0" "SCAN TABLE user_checkin" "0" "0" "0" "EXECUTE LIST SUBQUERY 1" "2" "0" "0" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=?)" "3" "0" "0" "SCAN TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1" "1" "0" "0" "COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)" EXPLAIN QUERY PLAN
 
                selectid order from detail "0" "0" "0" " SCAN TABLE user_checkin" "0" "0" "0" "EXECUTE LIST SUBQUERY 1" "2" "0" "0" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=?)" "3" "0" "0" "SCAN TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1" "1" "0" "0" "COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)" user_id SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN ( SELECT friend2 FROM friendship WHERE friend1 = :my_id UNION SELECT friend1 FROM friendship WHERE friend2 = :my_id ) Y
 
                selectid order from detail "0" "0" "0" " SCAN TABLE user_checkin" "0" "0" "0" "EXECUTE LIST SUBQUERY 1" "2" "0" "0" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=?)" "3" "0" "0" "SCAN TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1" "1" "0" "0" "COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)" user_id CREATE INDEX userIdIndex
ON user_checkin(user_id); SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN ( SELECT friend2 FROM friendship WHERE friend1 = :my_id UNION SELECT friend1 FROM friendship WHERE friend2 = :my_id ) Y
 
                selectid order from detail "0" "0" "0" " SCAN TABLE user_checkin" "0" "0" "0" "EXECUTE LIST SUBQUERY 1" "2" "0" "0" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=?)" "3" "0" "0" " SCAN TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1" "1" "0" "0" "COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)"
userIdIndex CREATE INDEX userIdIndex
ON user_checkin(user_id); friend 2 SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN ( SELECT friend2 FROM friendship WHERE friend1 = :my_id UNION SELECT friend1 FROM friendship WHERE friend2 = :my_id ) Y
 
                selectid order from detail "0" "0" "0" " SCAN TABLE user_checkin" "0" "0" "0" "EXECUTE LIST SUBQUERY 1" "2" "0" "0" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=?)" "3" "0" "0" " SCAN TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1" "1" "0" "0" "COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)"
friend2Index
userIdIndex CREATE INDEX userIdIndex
ON user_checkin(user_id); friend 2 CREATE INDEX friend2Index
ON friendship(friend2); SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN ( SELECT friend2 FROM friendship WHERE friend1 = :my_id UNION SELECT friend1 FROM friendship WHERE friend2 = :my_id ) Y
 
                SELECT count( DISTINCT checkin_id) FROM user_checkin WHERE user_id IN (
SELECT friend2
FROM friendship
WHERE friend1 = :my_id
UNION SELECT friend1
FROM friendship
WHERE friend2 = :my_id ) Y selectid order from detail "0" "0" "0" " SEARCH TABLE user_checkin USING INDEX
(user_id=?)" "0" "0" "0" "EXECUTE LIST SUBQUERY 1" "2" "0" "0" "SEARCH TABLE friendship USING COVERING INDEX sqlite_autoindex_friendship_1 (friend1=?)" "3" "0" "0" " SEARCH TABLE friendship USING INDEX
(friend2=?)" "1" "0" "0" "COMPOUND SUBQUERIES 2 AND 3 USING TEMP B-TREE (UNION)" CREATE INDEX userIdIndex
ON user_checkin(user_id);
CREATE INDEX friend2Index
ON friendship(friend2);
friend2Index
userIdIndex
 
                SQL
 
                SQLite and Android class MyDatabase(context: Context, name: String?)
: SQLiteOpenHelper(context, name, 
null , VERSION ) {
companion object {
1
} Y
} Z
 
                SQLite and Android class MyDatabase(context: Context, name: String?)
: SQLiteOpenHelper(context, name, 
null , VERSION ) {
override fun onUpgrade(db: SQLiteDatabase, old: Int, new : Int) { } X
companion object {
1
} Y
} Z
 
                SQLite and Android class MyDatabase(context: Context, name: String?)
: SQLiteOpenHelper(context, name, 
null , VERSION ) {
override fun onCreate(db: SQLiteDatabase) { } W
override fun onUpgrade(db: SQLiteDatabase, old: Int, new : Int) { } X
companion object {
1
} Y
} Z
 
                SQLite and Android class MyDatabase(context: Context, name: String?)
: SQLiteOpenHelper(context, name, 
null , VERSION ) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(
""" CREATE TABLE user ( _id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL ); """ . trimIndent ())
} W
override fun onUpgrade(db: SQLiteDatabase, old: Int, new : Int) { } X
companion object {
1
} Y
} Z
 
                SQLite and Android class MyDatabase(context: Context, name: String?)
: SQLiteOpenHelper(context, name, 
null , VERSION ) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(
""" CREATE TABLE user ( Z
    _id 
INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL ); """ . trimIndent ())
} W
override fun onUpgrade(db: SQLiteDatabase, old: Int, new : Int) { } X
companion object {
1
} Y
} Z object UserColumns {
"user"
"_id"
"name" } A
 
                SQLite and Android class MyDatabase(context: Context, name: String?)
: SQLiteOpenHelper(context, name, 
null , VERSION ) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(
""" CREATE TABLE ${ UserColumns. TABLE_NAME } ( Z
${ UserColumns. ID } INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
${ UserColumns. NAME } TEXT NOT NULL ); """ . trimIndent ())
} W
override fun onUpgrade(db: SQLiteDatabase, old: Int, new : Int) { } X
companion object {
1
} Y
} Z object UserColumns {
"user"
"_id"
"name" } A user
    _id 
    
name
 
                SQLite and Android class MyDatabase(context: Context, name: String?)
: SQLiteOpenHelper(context, name, 
null , VERSION ) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(
""" CREATE TABLE ${ UserColumns. TABLE_NAME } ( Z
${ UserColumns. ID } INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
${ UserColumns. NAME } TEXT NOT NULL ); """ . trimIndent ())
} W
override fun onUpgrade(db: SQLiteDatabase, old: Int, new : Int) { } X
companion object {
1
} Y
} Z object UserColumns {
"user"
"_id"
"name" } A object FriendshipColumns {
"friendship"
"friend1"
"friend2"
"became_friends" } B
 
                SQLite and Android class MyDatabase(context: Context, name: String?)
: SQLiteOpenHelper(context, name, 
null , VERSION ) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(
""" CREATE TABLE ${ UserColumns. TABLE_NAME } ( Z
${ UserColumns. ID } INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
${ UserColumns. NAME } TEXT NOT NULL ); """ . trimIndent ())
db.execSQL(
""" CREATE TABLE ${ FriendshipColumns. TABLE_NAME } (
${ FriendshipColumns. FRIEND_1 } INTEGER NOT NULL REFERENCES ${ UserColumns. TABLE_NAME } ,
${ FriendshipColumns. FRIEND_2 } INTEGER NOT NULL REFERENCES ${ UserColumns. TABLE_NAME } ,
${ FriendshipColumns. BECAME_FRIENDS } INTEGER NOT NULL DEFAULT CURRENT_TIME, PRIMARY KEY ( ${ FriendshipColumns. FRIEND_1 } , ${ FriendshipColumns. FRIEND_2 } ) ); """ . trimIndent ())
} W
override fun onUpgrade(db: SQLiteDatabase, old: Int, new : Int) { } X
companion object {
1
} Y
} Z object UserColumns {
"user"
"_id"
"name" } A object FriendshipColumns {
"friendship"
"friend1"
"friend2"
"became_friends" } B
 
                SQLite and Android class MyDatabase(context: Context, name: String?)
: SQLiteOpenHelper(context, name, 
null , VERSION ) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(
""" CREATE TABLE ${ UserColumns. TABLE_NAME } ( Z
${ UserColumns. ID } INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
${ UserColumns. NAME } TEXT NOT NULL ); """ . trimIndent ())
db.execSQL(
""" CREATE TABLE ${ FriendshipColumns. TABLE_NAME } (
${ FriendshipColumns. FRIEND_1 } INTEGER NOT NULL REFERENCES ${ UserColumns. TABLE_NAME } ,
${ FriendshipColumns. FRIEND_2 } INTEGER NOT NULL REFERENCES ${ UserColumns. TABLE_NAME } ,
${ FriendshipColumns. BECAME_FRIENDS } INTEGER NOT NULL DEFAULT CURRENT_TIME, PRIMARY KEY ( ${ FriendshipColumns. FRIEND_1 } , ${ FriendshipColumns. FRIEND_2 } ) ); """ . trimIndent ())
} W
override fun onUpgrade(db: SQLiteDatabase, old: Int, new : Int) { } X
companion object {
1
} Y
} Z object UserColumns {
"user"
"_id"
"name" } A object FriendshipColumns {
"friendship"
"friend1"
"friend2"
"became_friends" } B object CheckinColumns {
"checkin"
"_id"
"name"
"time" } C
 
                SQLite and Android class MyDatabase(context: Context, name: String?)
: SQLiteOpenHelper(context, name, 
null , VERSION ) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(
""" CREATE TABLE ${ UserColumns. TABLE_NAME } ( Z
${ UserColumns. ID } INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
${ UserColumns. NAME } TEXT NOT NULL ); """ . trimIndent ())
db.execSQL(
""" CREATE TABLE ${ FriendshipColumns. TABLE_NAME } (
${ FriendshipColumns. FRIEND_1 } INTEGER NOT NULL REFERENCES ${ UserColumns. TABLE_NAME } ,
${ FriendshipColumns. FRIEND_2 } INTEGER NOT NULL REFERENCES ${ UserColumns. TABLE_NAME } ,
${ FriendshipColumns. BECAME_FRIENDS } INTEGER NOT NULL DEFAULT CURRENT_TIME, PRIMARY KEY ( ${ FriendshipColumns. FRIEND_1 } , ${ FriendshipColumns. FRIEND_2 } ) ); """ . trimIndent ())
db.execSQL(
""" CREATE TABLE ${ CheckinColumns. TABLE_NAME } (
${ CheckinColumns. ID } INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
${ CheckinColumns. NAME } TEXT NOT NULL,
${ CheckinColumns. TIME } INTEGER NOT NULL ); """ . trimIndent ())
} W
override fun onUpgrade(db: SQLiteDatabase, old: Int, new : Int) { } X
companion object {
1
} Y
} Z object UserColumns {
"user"
"_id"
"name" } A object FriendshipColumns {
"friendship"
"friend1"
"friend2"
"became_friends" } B object CheckinColumns {
"checkin"
"_id"
"name"
"time" } C
 
                SQLite and Android class MyDatabase(context: Context, name: String?)
: SQLiteOpenHelper(context, name, 
null , VERSION ) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(
""" CREATE TABLE ${ UserColumns. TABLE_NAME } ( Z
${ UserColumns. ID } INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
${ UserColumns. NAME } TEXT NOT NULL ); """ . trimIndent ())
db.execSQL(
""" CREATE TABLE ${ FriendshipColumns. TABLE_NAME } (
${ FriendshipColumns. FRIEND_1 } INTEGER NOT NULL REFERENCES ${ UserColumns. TABLE_NAME } ,
${ FriendshipColumns. FRIEND_2 } INTEGER NOT NULL REFERENCES ${ UserColumns. TABLE_NAME } ,
${ FriendshipColumns. BECAME_FRIENDS } INTEGER NOT NULL DEFAULT CURRENT_TIME, PRIMARY KEY ( ${ FriendshipColumns. FRIEND_1 } , ${ FriendshipColumns. FRIEND_2 } ) ); """ . trimIndent ())
db.execSQL(
""" CREATE TABLE ${ CheckinColumns. TABLE_NAME } (
${ CheckinColumns. ID } INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
${ CheckinColumns. NAME } TEXT NOT NULL,
${ CheckinColumns. TIME } INTEGER NOT NULL ); """ . trimIndent ())
} W
override fun onUpgrade(db: SQLiteDatabase, old: Int, new : Int) { } X
companion object {
1
} Y
} Z object UserColumns {
"user"
"_id"
"name" } A object FriendshipColumns {
"friendship"
"friend1"
"friend2"
"became_friends" } B object CheckinColumns {
"checkin"
"_id"
"name"
"time" } C object UserCheckinColumns {
"user_checkin"
"checkin_id"
"user_id" } D
 
                SQLite and Android class MyDatabase(context: Context, name: String?) : SQLiteOpenHelper(context, name, null , VERSION ) {
override fun onCreate(db: SQLiteDatabase) { db.execSQL( """ CREATE TABLE ${ UserColumns. TABLE_NAME } (
${ UserColumns. ID } INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
${ UserColumns. NAME } TEXT NOT NULL ); """ . trimIndent ()) db.execSQL( """ CREATE TABLE ${ FriendshipColumns. TABLE_NAME } (
${ FriendshipColumns. FRIEND_1 } INTEGER NOT NULL REFERENCES ${ UserColumns. TABLE_NAME } ,
${ FriendshipColumns. FRIEND_2 } INTEGER NOT NULL REFERENCES ${ UserColumns. TABLE_NAME } ,
${ FriendshipColumns. BECAME_FRIENDS } INTEGER NOT NULL DEFAULT CURRENT_TIME, PRIMARY KEY ( ${ FriendshipColumns. FRIEND_1 } , ${ FriendshipColumns. FRIEND_2 } ) ); """ . trimIndent ()) db.execSQL( """ CREATE TABLE ${ CheckinColumns. TABLE_NAME } (
${ CheckinColumns. ID } INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
${ CheckinColumns. NAME } TEXT NOT NULL,
${ CheckinColumns. TIME } INTEGER NOT NULL ); """ . trimIndent ()) db.execSQL( """ CREATE TABLE ${ UserCheckinColumns. TABLE_NAME } (
${ UserCheckinColumns. CHECKIN_ID } INTEGER NOT NULL REFERENCES ${ CheckinColumns. TABLE_NAME } ,
${ UserCheckinColumns. USER_ID } INTEGER NOT NULL REFERENCES ${ UserColumns. TABLE_NAME } , PRIMARY KEY( ${ UserCheckinColumns. CHECKIN_ID } , ${ UserCheckinColumns. USER_ID } ) ); """ . trimIndent ()) } W
override fun onUpgrade(db: SQLiteDatabase, old: Int, new : Int) { } X
companion object {
1
} Y
} Z object UserColumns {
"user"
"_id"
"name" } A object FriendshipColumns {
"friendship"
"friend1"
"friend2"
"became_friends" } B object CheckinColumns {
"checkin"
"_id"
"name"
"time" } C object UserCheckinColumns {
"user_checkin"
"checkin_id"
"user_id" } D
 
                SQLite and Android fun friendsCheckins(db: SQLiteDatabase, myId: Int): Cursor? {
return db.rawQuery( """ SELECT count(DISTINCT ${ UserCheckinColumns. CHECKIN_ID } ) FROM ${ UserCheckinColumns. TABLE_NAME } WHERE ${ UserCheckinColumns. USER_ID } IN ( SELECT ${ FriendshipColumns. FRIEND_2 } FROM ${ FriendshipColumns. TABLE_NAME } WHERE ${ FriendshipColumns. FRIEND_1 } = ?1 UNION SELECT ${ FriendshipColumns. FRIEND_1 } FROM ${ FriendshipColumns. TABLE_NAME } WHERE ${ FriendshipColumns. FRIEND_2 } = ?1 ) """ . trimIndent (), arrayOf (myId.toString())) } Z
 
                SQLite and Android fun friendsCheckins(db: SQLiteDatabase, myId: Int): Cursor? {
return db.rawQuery( """ SELECT count(DISTINCT ${ UserCheckinColumns. CHECKIN_ID } ) FROM ${ UserCheckinColumns. TABLE_NAME } WHERE ${ UserCheckinColumns. USER_ID } IN ( SELECT ${ FriendshipColumns. FRIEND_2 } FROM ${ FriendshipColumns. TABLE_NAME } WHERE ${ FriendshipColumns. FRIEND_1 } = ?1 UNION SELECT ${ FriendshipColumns. FRIEND_1 } FROM ${ FriendshipColumns. TABLE_NAME } WHERE ${ FriendshipColumns. FRIEND_2 } = ?1 ) """ . trimIndent (), arrayOf (myId.toString())) } Z
 
                SQLite and Android fun friendsCheckins(db: SQLiteDatabase, myId: Int): Cursor? { A
return db.rawQuery( """ SELECT count(DISTINCT ${ UserCheckinColumns. CHECKIN_ID } ) FROM ${ UserCheckinColumns. TABLE_NAME } WHERE ${ UserCheckinColumns. USER_ID } IN ( SELECT ${ FriendshipColumns. FRIEND_2 } FROM ${ FriendshipColumns. TABLE_NAME } WHERE ${ FriendshipColumns. FRIEND_1 } = ?1 UNION SELECT ${ FriendshipColumns. FRIEND_1 } FROM ${ FriendshipColumns. TABLE_NAME } WHERE ${ FriendshipColumns. FRIEND_2 } = ?1 ) B
""" . trimIndent (), arrayOf (myId.toString())) } Z
 
                SQLite and Android fun friendsCheckins(db: SQLiteDatabase, myId: Int): Int { A
return db.rawQuery( """ SELECT count(DISTINCT ${ UserCheckinColumns. CHECKIN_ID } ) FROM ${ UserCheckinColumns. TABLE_NAME } WHERE ${ UserCheckinColumns. USER_ID } IN ( SELECT ${ FriendshipColumns. FRIEND_2 } FROM ${ FriendshipColumns. TABLE_NAME } WHERE ${ FriendshipColumns. FRIEND_1 } = ?1 UNION SELECT ${ FriendshipColumns. FRIEND_1 } FROM ${ FriendshipColumns. TABLE_NAME } WHERE ${ FriendshipColumns. FRIEND_2 } = ?1 ) B
""" . trimIndent (), arrayOf (myId.toString())) } Z
 
                SQLite and Android fun friendsCheckins(db: SQLiteDatabase, myId: Int): Int { A
val cursor = db.rawQuery( """ SELECT count(DISTINCT ${ UserCheckinColumns. CHECKIN_ID } ) FROM ${ UserCheckinColumns. TABLE_NAME } WHERE ${ UserCheckinColumns. USER_ID } IN ( SELECT ${ FriendshipColumns. FRIEND_2 } FROM ${ FriendshipColumns. TABLE_NAME } WHERE ${ FriendshipColumns. FRIEND_1 } = ?1 UNION SELECT ${ FriendshipColumns. FRIEND_1 } FROM ${ FriendshipColumns. TABLE_NAME } WHERE ${ FriendshipColumns. FRIEND_2 } = ?1 ) B
""" . trimIndent (), arrayOf (myId.toString()))
cursor. use {
if ( it .moveToNext()) {
return it .getInt( 0 ) G
}
J
throw IllegalStateException( "Query returned zero rows" )
} K
} Z
 
                SQLite and Android fun friendsCheckins(db: SQLiteDatabase, myId: Int): Int { A
val
count = "checkin_count"
val cursor = db.rawQuery( """ SELECT count(DISTINCT ${ UserCheckinColumns. CHECKIN_ID } ) AS $ count
FROM 
${ UserCheckinColumns. TABLE_NAME } WHERE ${ UserCheckinColumns. USER_ID } IN ( SELECT ${ FriendshipColumns. FRIEND_2 } FROM ${ FriendshipColumns. TABLE_NAME } WHERE ${ FriendshipColumns. FRIEND_1 } = ?1 UNION SELECT ${ FriendshipColumns. FRIEND_1 } FROM ${ FriendshipColumns. TABLE_NAME } WHERE ${ FriendshipColumns. FRIEND_2 } = ?1 ) B
""" . trimIndent (), arrayOf (myId.toString()))
cursor. use {
if ( it .moveToNext()) {
return it .getInt( it .getColumnIndex(count)) G
}
J
throw IllegalStateException( "Query returned zero rows" )
} K
} Z
 
                SQLite and Android fun friendsCheckins(db: SQLiteDatabase, myId: Int): Int { A
val
count = "checkin_count"
val cursor = db.rawQuery( """ SELECT count(DISTINCT ${ UserCheckinColumns. CHECKIN_ID } ) AS $ count
FROM 
${ UserCheckinColumns. TABLE_NAME } WHERE ${ UserCheckinColumns. USER_ID } IN ( SELECT ${ FriendshipColumns. FRIEND_2 } FROM ${ FriendshipColumns. TABLE_NAME } WHERE ${ FriendshipColumns. FRIEND_1 } = ?1 UNION SELECT ${ FriendshipColumns. FRIEND_1 } FROM ${ FriendshipColumns. TABLE_NAME } WHERE ${ FriendshipColumns. FRIEND_2 } = ?1 ) B
""" . trimIndent (), arrayOf (myId.toString()))
cursor. use {
if ( it .moveToNext()) {
return it .getInt( it .getColumnIndex(count)) G
}
J
throw IllegalStateException( "Query returned zero rows" )
} K
} Z
 
                SQLite and Android • Strings... Strings everywhere...
 
                SQLite and Android • Strings... Strings everywhere... • No query or type safety
 
                SQLite and Android • Strings... Strings everywhere... • No query or type safety • Prohibitive level of verbosity
 
                SQLite and Android • Strings... Strings everywhere... • No query or type safety • Prohibitive level of verbosity • Reactive updates only achievable through a ContentProvider • Another level of hell, omitted for sanity
 
                SQLDelight & Room
 
                SQLDelight Room
 
                SQLDelight CREATE TABLE todo_list ( _id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT , name TEXT NOT NULL , archived INTEGER AS Boolean NOT NULL DEFAULT 0 ); @AutoValue public abstract class TodoList implements Parcelable, TodoListModel {
new TodoListModel.Factory<>(AutoValue_TodoList:: new ); }
 
                @Entity ( tableName = "todo_list" ) data class TodoList(
@PrimaryKey ( autoGenerate = true )
@ColumnInfo ( name = "_id" )
val id : Int = 0 ,
val name : String,
val archived : Boolean = false ) Room
 
                SQLDelight Room • No restriction on Java or SQL type
 
                SQLDelight Room • No restriction on Java or SQL type • No restriction on Java type ( @Ignore )
 
                SQLDelight Room • No restriction on Java or SQL type • No inheritance or other OOP • No restriction on Java type ( @Ignore )
 
                SQLDelight Room • No restriction on Java or SQL type • No inheritance or other OOP • No restriction on Java type ( @Ignore ) • Subset of SQLite supported • UNIQUE , CHECK , DEFAULT
 
                SQLDelight Room • No restriction on Java or SQL type • No inheritance or other OOP • Doesn't play well with Kotlin data
classes • No restriction on Java type ( @Ignore ) • Subset of SQLite supported • UNIQUE , CHECK , DEFAULT
 
                SQLDelight Room • No restriction on Java or SQL type • No inheritance or other OOP • Doesn't play well with Kotlin data
classes • No restriction on Java type ( @Ignore ) • Subset of SQLite supported • UNIQUE , CHECK , DEFAULT • Doesn't work with AutoValue
 
                SQLDelight CREATE TABLE todo_item ( _id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT , todo_list_id INTEGER NOT NULL REFERENCES todo_list, description TEXT NOT NULL , complete INTEGER AS Boolean NOT NULL DEFAULT 0 );
createListIdIndex: CREATE INDEX item_list_id ON todo_item(todo_list_id); @AutoValue public abstract class TodoItem implements TodoItemModel, Parcelable {
new Factory<>(AutoValue_TodoItem:: new ); }
 
                @Entity (
tableName = "todo_item" ,
foreignKeys = arrayOf (ForeignKey(
entity = TodoItem:: class ,
parentColumns = arrayOf ( "_id" ),
childColumns = arrayOf ( "todo_list_id" ) )) ) data class TodoItem(
@PrimaryKey ( autoGenerate = true )
@ColumnInfo ( name = "_id" )
val id : Long,
@ColumnInfo ( name = "todo_list_id" , index = true )
val todoListId : Long,
val description : String,
val complete : Boolean = false ) Room
 
                SQLDelight insertList: INSERT INTO todo_list (name) VALUES (?); private val insertList : InsertList by lazy {
InsertList( db . writableDatabase ) }
... db . bindAndExecute ( insertList ) { bind(name) }
 
                @Insert fun insert(list: TodoList) Room listDao .insert(TodoList( name = name))
 
                SQLDelight Room • Can’t insert an object • Can only insert objects
 
                SQLDelight Room • Can’t insert an object • Verbose - requires you maintain the cache of mutator queries • Can only insert objects
 
                SQLDelight Room • Can’t insert an object • Verbose - requires you maintain the cache of mutator queries • Can only insert objects • Can’t use DAO’s during creation
 
                SQLDelight titleAndCount: SELECT name, count(todo_item._id) FROM todo_list LEFT JOIN todo_item ON (todo_list._id = todo_list_id) WHERE todo_list._id = ? AND complete = 0 GROUP BY todo_list._id; @AutoValue public abstract class TitleAndCount implements TitleAndCountModel {
public static final TitleAndCountCreator CREATOR
= AutoValue_TitleAndCount:: new ; }
 
                @Query ( "" +
"SELECT name, count(*) AS count
" +
"FROM todo_list
" +
"LEFT JOIN todo_item ON (todo_list._id = todo_list_id)
" +
"WHERE todo_list._id = :todoListId AND complete = 0
" +
"GROUP BY todo_list._id" ) fun titleAndCount(todoListId: Long): Flowable<TitleAndCount> Room data class TitleAndCount(
val name : String,
val count : Int )
 
                SQLDelight Room • “Not sure how to convert a Cursor to this method's return type ”
 
                SQLDelight Room • “Not sure how to convert a Cursor to this method's return type ” • Not
type safe
 
                Not
type safe data class TitleAndCount(
val name : String,
val
count
: Int
)
Y
println
(
name
)
// Grocery List
println
(
count
)
// 4
 
                Not
type safe data class TitleAndCount(
val name : String,
val count : Int ) Y
 
                Not type safe data class TitleAndCount(
val names : String,
val count : Int ) Y java.lang.IllegalArgumentException: Parameter specified as non-null is null
Int
 
                Not
type safe data class TitleAndCount(
val name : String,
val count : Int ) Y
 
                Not type safe data class TitleAndCount(
val name : Int,
val
count
: Int
)
Y
println
(
name
)
// 0
println
(
count
)
// 4
 
                Room itemDao .titleAndCount( listId ) .observeOn(AndroidSchedulers.mainThread()) .subscribe { titleAndCount ->
TODO ()
}
 
                SQLDelight db . createQuery (TodoItem. FACTORY .titleAndCount( listId )) .mapToOne(TitleAndCount. MAPPER ::map) .observeOn(AndroidSchedulers.mainThread()) .subscribe {
TODO ()
} X
TodoItem.
FACTORY .titleAndCountMapper( CREATOR );
 
                SQLDelight db . createQuery (TodoItem. FACTORY .titleAndCount( listId )) .mapToOne(TitleAndCount. MAPPER ::map) .observeOn(AndroidSchedulers.mainThread()) .subscribe {
TODO ()
} X
 
                Not type safe db . createQuery (TodoItem. FACTORY .titleAndCount( listId ))
// Wreak havoc 
.mapToOne(TitleAndCount.
MAPPER ::map) .observeOn(AndroidSchedulers.mainThread()) .subscribe {
TODO ()
} X
SQLDelight
 
                SQLDelight Room • “Not sure how to convert a Cursor to this method's return type ” • Not type safe
 
                SQLDelight Room • Verbose calling code • “Not sure how to convert a Cursor to this method's return type ” • Not type safe
 
                SQLDelight Room • Verbose calling code • SQLBrite - SQLDelight bridge not type safe • “Not sure how to convert a Cursor to this method's return type ” • Not type safe
 
                SQLDelight Room • Full DDL/DML support • Views, Triggers, Indexes
 
                SQLDelight Room • Full DDL/DML support • Views, Triggers, Indexes • Compiler error == IDE error
 
                SQLDelight Room • Full DDL/DML support • Views, Triggers, Indexes • Compiler error == IDE error • Better tooling • Find usages, refactoring, auto complete
 
                SQLDelight Room • Full DDL/DML support • Views, Triggers, Indexes • Compiler error == IDE error • Better tooling • Find usages, refactoring, auto complete • Migration testing utilities
 
                SQLDelight Room • Full DDL/DML support • Views, Triggers, Indexes • Compiler error == IDE error • Better tooling • Find usages, refactoring, auto complete • Migration testing utilities • Embedded object types
 
                SQLDelight Room • Full DDL/DML support • Views, Triggers, Indexes • Compiler error == IDE error • Better tooling • Find usages, refactoring, auto complete • Migration testing utilities • Embedded object types • Better support/documentation
 
                SQLDelight Room
 
                SQLDelight Room • Associate a java type to a column definition and receive type safe projections and mutation apis. • Define a table in java and serialize java objects to and from a query
 
                Room • Unless you have a reason to otherwise, use Room
 
                Room • Unless you have a reason to otherwise, use Room • Support and documentation is way better
 
                Room • Unless you have a reason to otherwise, use Room • Support and documentation is way better • More than enough sqlite support to get the benefits you need
 
                Room • Unless you have a reason to otherwise, use Room • Support and documentation is way better • More than enough sqlite support to get the benefits you need • API feels familiar and simple — akin to Retrofit
 
                SQLDelight • Spending a lot of time in SQLite → Better tooling
 
                SQLDelight • Spending a lot of time in SQLite → Better tooling • Complicated models → Type Safety
 
                SQLDelight • Spending a lot of time in SQLite → Better tooling • Complicated models → Type Safety • Complicated client backend → Views, Triggers, Virtual Tables, Inserts
 
                SQLDelight 1.0 • working-kotlin branch on GitHub
 
                SQLDelight pre-1.0 CREATE TABLE todo_list ( _id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT , name TEXT NOT NULL , archived INTEGER AS Boolean NOT NULL DEFAULT 0 ); @AutoValue public abstract class TodoList implements Parcelable, TodoListModel {
new TodoListModel.Factory<>(AutoValue_TodoList:: new ); }
 
                SQLDelight 1.0 CREATE TABLE todo_list ( _id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT , name TEXT NOT NULL , archived INTEGER AS Boolean NOT NULL DEFAULT 0 );
 
                insertList: INSERT INTO todo_list (name) VALUES (?); private val insertList : InsertList by lazy {
InsertList( db . writableDatabase ) }
... db . bindAndExecute ( insertList ) { bind(name) } SQLDelight pre-1.0
 
                SQLDelight 1.0 insertList: INSERT INTO todo_list (name) VALUES (?); db .insertList( name)
 
                db . createQuery (TodoItem. FACTORY .titleAndCount( listId )) .mapToOne(TitleAndCount. MAPPER ::map) .observeOn(AndroidSchedulers.mainThread()) .subscribe {
TODO ()
} X
TodoItem.
FACTORY .titleAndCountMapper( CREATOR ); SQLDelight pre-1.0 .observe()
 
                SQLDelight 1.0 db .titleAndCount( listId ).observe() .mapToOne()
.observeOn(AndroidSchedulers.mainThread()) 
.subscribe 
{
TODO ()
} X
public static final RowMapper<TodoItem> MAPPER = TodoItem. FACTORY .titleAndCountMapper( CREATOR );
 
                SQLDelight 1.0 db .titleAndCount(::CustomType, listId ).observe() .mapToOne()
.observeOn(AndroidSchedulers.mainThread()) 
.subscribe 
{
TODO ()
} X
fun <T> titleAndCount( mapper: (title: String, count: Int) -> T, listId: Long ): Query<T>
 
                SQLDelight 1.0 • working-kotlin branch on GitHub
 
                SQLDelight 1.0 • working-kotlin branch on GitHub • Increase precision of observable emissions • Only possible because SQLDelight is a compiler
 
                SQLDelight 1.0 • working-kotlin branch on GitHub • Increase precision of observable emissions • Only possible because SQLDelight is a compiler • Embrace Kotlin as the future
 
                Future of SQLite on Android • SupportSQLite
 
                SupportSQLite SQLiteDatabase SQLiteOpenHelper SQLiteProgram SQLiteStatement
 
                SupportSQLite SupportSQLiteDatabase SupportSQLiteOpenHelper SupportSQLiteProgram SupportSQLiteStatement
 
                SupportSQLite SupportSQLiteDatabase SupportSQLiteOpenHelper SupportSQLiteProgram SupportSQLiteStatement SupportSQLiteQuery
SupportSQLiteDatabase.query(supportQuery)
 
                SupportSQLite api 'android.arch.persistence:db:1.0.0-beta1'
 
                SupportSQLite api 'android.arch.persistence:db:1.0.0-beta1'
implementation 'android.arch.persistence:db-framework :1.0.0-beta1'
 
                Future of SQLite on Android • SupportSQLite • Paging
 
                Paging • Enables efficient paging of large data sources
 
                Paging • Enables efficient paging of large data sources • Not tied to SQL, Room, or RecyclerView
 
                Paging • Enables efficient paging of large data sources • Not tied to SQL, Room, or RecyclerView • Seamless Room support @Query ( "select * from users WHERE age > :age order by name DESC" ) fun usersOlderThan(age: Int): TiledDataSource<User>
 
                SELECT * FROM persistence_solution WHERE type != 'flat'
AND type != 'ORM'
AND type != 'ObjectDB'
https://goo.gl/aYQnfh @Strongolopolis & @JakeWharton
The Resurgence of SQL
 
                https://goo.gl/aYQnfh @Strongolopolis & @JakeWharton The Resurgence of SQL
