4. • Runtime Queries (default),
optional API for Compile-Time
• APIs for Async, Streaming,
Sync (3rd party), and Effect-Type
tracking.
• Largest Contributors:
Stefan Zeiger (170k),
Christopher Vogt (11k),
Hemant Kumar (1.7k)
• Compile Time Queries (default),
automatic fallback to Runtime
• APIs for Sync, Async, and via
Finagle. Only Streaming for
Cassandra.
• Largest Contributors:
Flavio Brasil (79k),
Mykhailo Osypov (5.4k),
Juliano Alves (2.8k),
Michael Ledin (2.7k),
Gustavo Amigo (2.6k),
jilen (2.4k),
Subhobrata Dey (2k),
5. Anatomy of a Slick Query
TableQuery[Person].filter(_.age > 10)
TableQuery[Person].filter(age =>
columnExtensionMethods(person.age).>(LiteralColumn[Int](10))
)((CanBeQueryCondition.BooleanColumnCanBeQueryCondition))
Filter(
from: Table(
PERSON,
Path(NAME), Path(AGE)…
)
where: Apply(
Function >
arg0: Path AGE
arg1: LiteralNode(10)
)
)
Expanded Implicits
Slick AST
select
NAME,
AGE…
from PERSON where AGE > 10
Query
Scala Compiler
~Scala Code
JDBC
Context
Evaluator
6. Anatomy of a Quill Query
quote{ query[Person].filter(_.age > 10) }
EntityQuery[Person]).filter(((x$1: Person) => x$1.age.>(10)))
select
NAME,
AGE…
from PERSON where AGE > 10
(Compile Time) Queries
querySchema("Person").filter(
x1 => x1.age > 10)
.map(x => (x.id, x.name, x.age))
Scala Compiler
Macro Engine
Quasi Quote Parser
Scala AST
Quill AST
7. Which one is Better? Slick Quill
Usability
Reliability
Extension Friendliness
Streaming
Testing
Ecosystem
Bonus
10. SELECT DISTINCT
account.name, alias,
CASE WHEN code = 'EV'
THEN cast(account.number AS VARCHAR)
ELSE cast(account.number AS VARCHAR) + substring(alias, 1, 2) END AS OFFICIAL_IDENTITY,
CASE WHEN order_permission IN ('A', 'S')
THEN 'ST' ELSE 'ENH' END
FROM (
SELECT DISTINCT mc.alias, mc.code, mc.order_permission, mc.account_tag
FROM MERCHANT_CLIENTS mc
JOIN REGISTRY r ON r.alias = mc.alias
WHERE r.market = 'us' AND r.record_type = 'M'
UNION ALL
SELECT DISTINCT sc.alias, 'EV' AS code, part.order_permission, sc.account_tag
FROM SERVICE_CLIENTS sc
JOIN REGISTRY r ON r.alias = sc.alias AND r.record_type = 'S' AND r.market = 'us'
JOIN PARTNERSHIPS part ON part.id = sc.partnership_fk) client
INNER JOIN (
dbo.ACCOUNTS account
INNER JOIN ACCOUNT_TYPES accountType ON account.type = accountType.account_type
LEFT JOIN DEDICATED_ACCOUNTS dedicated ON dedicated.account_number = account.number)
ON (accountType.mapping_type = 0)
OR (accountType.mapping_type = 2 AND account.tag = client.account_tag)
OR (accountType.mapping_type = 1 AND dedicated.client_alias = client.alias)
11.
12. SELECT DISTINCT
account.name, alias,
CASE (...) AS OFFICIAL_IDENTITY,
CASE (...)
FROM (
SELECT DISTINCT
mc.alias, mc.code, mc.order_permission, mc.account_tag (code, alias, perm, tag)
FROM MERCHANT_CLIENTS mc
JOIN REGISTRY r ON (alias) AND (otherConditions)
UNION ALL
SELECT DISTINCT
sc.alias, 'EV' AS code, part.order_permission, sc.account_tag (code, alias, perm, tag)
FROM SERVICE_CLIENTS sc
JOIN REGISTRY r ON (alias) AND (otherConditions)
JOIN PARTNERSHIPS part ON (id <-> fk)) client
INNER JOIN (
dbo.ACCOUNTS account
INNER JOIN ACCOUNT_TYPES accountType ON (account_type)
LEFT JOIN DEDICATED_ACCOUNTS dedicated ON (account_number)
)
ON (possibly anything...)
OR (possibly the account tag...)
OR (possibly the alias...) → All Depending on the accountType
13. SELECT DISTINCT
account.name, alias,
CASE (...) AS OFFICIAL_IDENTITY,
CASE (...)
FROM (
SELECT DISTINCT
mc.alias, mc.code, mc.order_permission, mc.account_tag (code, alias, perm, tag)
FROM MERCHANT_CLIENTS mc
JOIN REGISTRY r ON (alias) AND (otherConditions)
UNION ALL
SELECT DISTINCT
sc.alias, 'EV' AS code, part.order_permission, sc.account_tag (code, alias, perm, tag)
FROM SERVICE_CLIENTS sc
JOIN REGISTRY r ON (alias) AND (otherConditions)
JOIN PARTNERSHIPS part ON (id <-> fk)) client
INNER JOIN (
dbo.ACCOUNTS account
INNER JOIN ACCOUNT_TYPES accountType ON (account_type)
LEFT JOIN DEDICATED_ACCOUNTS dedicated ON (account_number)
)
ON (possibly anything...)
OR (possibly the account tag...)
OR (possibly the alias...) → All Depending on the accountType
14. CREATE FUNCTION dbo.merchantClientsUdf (@market)
RETURNS table as RETURN (
SELECT DISTINCT
alias, code, order_permission, account_tag
FROM MERCHANT_CLIENTS merchantClient
JOIN REGISTRY entry
ON entry.alias = merchantClient.alias
WHERE entry.market = @market
AND entry.record_type = 'M')
CREATE VIEW CLIENT_ACCOUNTS AS
SELECT DISTINCT
account.name, alias,
CASE WHEN code = 'EV'
THEN cast(account.number AS VARCHAR)
ELSE cast(account.number AS VARCHAR) + substring(alias, 1, 2) END AS OFFICIAL_IDENTITY,
CASE WHEN order_permission IN ('A', 'S')
THEN 'ST' ELSE 'ENH' END
FROM (select * from merchantClientsUdf ('us') union
select * from serviceClientsUdf ('us')) as client
INNER JOIN (
dbo.ACCOUNTS account
INNER JOIN ACCOUNT_TYPES accountType ON account.type = accountType.account_type
LEFT JOIN DEDICATED_ACCOUNTS dedicated ON dedicated.account_number = account.number)
ON (accountType.mapping_type = 0)
OR (accountType.mapping_type = 2 AND account.tag = client.account_tag)
OR (accountType.mapping_type = 1 AND dedicated.client_alias = client.alias)
CREATE FUNCTION dbo.serviceClientsUdf (@market)
RETURNS table as RETURN (
SELECT DISTINCT
alias, code, order_permission, account_tag
FROM SERVICE_CLIENTS serviceClient
JOIN REGISTRY entry
ON entry.alias = serviceClient.alias
AND entry.record_type = 'S'
AND entry.market = @market
JOIN PARTNERSHIPS partnership
ON partnership.id = serviceClient.partnership_fk)
15. CREATE FUNCTION dbo.merchantClientsUdf (@market)
RETURNS table as RETURN (…)
CREATE VIEW EU_CLIENT_ACCOUNTS AS
SELECT DISTINCT
account.name, alias,
CASE WHEN code = 'EV'
THEN cast(account.number AS VARCHAR)
ELSE cast(account.number AS VARCHAR) + substring(alias, 1, 2) END AS OFFICIAL_IDENTITY,
CASE WHEN order_permission IN ('A', 'S')
THEN 'ST' ELSE 'ENH' END
FROM (select * from merchantClientsUdf ('eu') union
select * from enhancedServiceClientsUdf ('eu')) as client
INNER JOIN (
dbo.ACCOUNTS account
INNER JOIN ACCOUNT_TYPES accountType ON account.type = accountType.account_type
LEFT JOIN DEDICATED_ACCOUNTS dedicated ON dedicated.account_number = account.number)
ON (accountType.mapping_type = 0)
OR (accountType.mapping_type = 2 AND account.tag = client.account_tag)
OR (accountType.mapping_type = 1 AND dedicated.client_alias = client.alias)
CREATE FUNCTION dbo.enhancedServiceClientsUdf (@market)
RETURNS table as RETURN (
SELECT DISTINCT
alias, code, order_permission, account_tag
FROM SERVICE_CLIENTS serviceClient
JOIN REGISTRY entry
ON (... entry.market = @market ...)
JOIN PARTNERSHIPS partnership ON (…)
JOIN PARTNERSHIP_CODES pc
ON partnership.ID = pc.partnership_fk
16. CREATE FUNCTION dbo.merchantClientsUdf (@market)
RETURNS table as RETURN (…)
CREATE VIEW CA_CLIENT_ACCOUNTS AS
SELECT DISTINCT
account.name, alias,
CASE WHEN code = 'EV'
THEN cast(account.number AS VARCHAR)
ELSE cast(account.number AS VARCHAR) + substring(alias, 1, 2) END AS OFFICIAL_IDENTITY,
CASE WHEN order_permission IN ('A', 'S')
THEN 'ST' ELSE 'ENH' END
FROM (select * from merchantClientsUdf ('ca')) as client
INNER JOIN (
dbo.ACCOUNTS account
INNER JOIN ACCOUNT_TYPES accountType ON account.type = accountType.account_type
LEFT JOIN DEDICATED_ACCOUNTS dedicated ON dedicated.account_number = account.number)
ON (accountType.mapping_type = 0)
OR (accountType.mapping_type = 2 AND account.tag = client.account_tag)
OR (accountType.mapping_type = 1 AND dedicated.client_alias = client.alias)
17. ~O(N)/2 Codebase Size Per N Business Units!
Tables:
• merchantClientsUd
f
• serviceClientsUdf
• CLIENT_ACCOUNTS
• merchantClientsUdf
• serviceClientsUdf
• US_CLIENT_ACCOUNTS
• enhancedServiceClientsUdf +
• EU_CLIENT_ACCOUNTS +
• CA_CLIENT_ACCOUNTS +
Tables:
= (Still) Lots of Technical Debt
20. def merchantClientsUdf(market:String):Query[(String, String, Char, String)] = {
for {
mc <- merchantClients
r <- registry
if (r.alias === mc.alias && r.market === market
&& r.recordType === 'M')
} yield (mc.alias, mc.code, mc.orderPermission, mc.accountTag)
}
SELECT DISTINCT mc.alias, mc.code, order_permission, mc.account_tag
FROM MERCHANT_CLIENTS mc
JOIN REGISTRY r ON r.alias = mc.alias
WHERE r.market = 'us' AND r.record_type = 'M'
21. def merchantClientsUdf(market:String):Query[(String, String, Char, String)] = {
for {
mc <- merchantClients
r <- registry
if (r.alias === mc.alias && r.market === market
&& r.recordType === 'M')
} yield (mc.alias, mc.code, mc.orderPermission, mc.accountTag)
}
SELECT DISTINCT mc.alias, mc.code, order_permission, mc.account_tag
FROM MERCHANT_CLIENTS mc
JOIN REGISTRY r ON r.alias = mc.alias
WHERE r.market = 'us' AND r.record_type = 'M'
This Is a Lie
23. def merchantClientsUdf(market:String):
Query[ClientLifted,
Client,Seq] =
{
for {
mc <- merchantClients
r <- registry
if (r.alias === mc.alias && r.market === market
&& r.recordType === 'M')
} yield (mc.alias, mc.code, mc.orderPermission, mc.accountTag)
}
SELECT DISTINCT mc.alias, mc.code, order_permission, mc.account_tag
FROM MERCHANT_CLIENTS mc
JOIN REGISTRY r ON r.alias = mc.alias
WHERE r.market = 'us' AND r.record_type = 'M'
The Truth Is
24. def merchantClientsUdf(market:String) = quote {
for {
mc <- merchantClients
r <- registry
if (r.alias == mc.alias && r.market == lift(market)
&& r.recordType == "M")
} yield (mc.alias, mc.code, mc.orderPermission, mc.accountTag)
}
SELECT DISTINCT mc.alias, mc.code, order_permission, mc.account_tag
FROM MERCHANT_CLIENTS mc
JOIN REGISTRY r ON r.alias = mc.alias
WHERE r.market = 'us' AND r.record_type = 'M'
Quill Is Similar
25. def merchantClientsUdf(market:String):
Quoted[Query[(String,String,Char,String)]]=
quote {
for {
mc <- merchantClients
r <- registry
if (r.alias == mc.alias && r.market == lift(market)
&& r.recordType == "M")
} yield (mc.alias, mc.code, mc.orderPermission, mc.accountTag)
}
SELECT DISTINCT mc.alias, mc.code, order_permission, mc.account_tag
FROM MERCHANT_CLIENTS mc
JOIN REGISTRY r ON r.alias = mc.alias
WHERE r.market = 'us' AND r.record_type = 'M'
… but with sane type signatures
26. def merchantClientsUdf(market:String):
Quoted[Query[(String,String,Char,String)]]=
quote {
for {
mc <- mercantClients
r <- registry
if (r.alias == mc.alias && r.market == lift(market)
&& r.recordType == "M")
} yield (mc.alias, mc.code, mc.orderPermission, mc.accountTag)
}
SELECT DISTINCT mc.alias, mc.code, order_permission, mc.account_tag
FROM MERCHANT_CLIENTS mc
JOIN REGISTRY r ON r.alias = mc.alias
WHERE r.market = 'us' AND r.record_type = 'M'
… but with sane type signatures
27. def merchantClientsUdf(market:String):
Quoted[Query[Client]]=
quote {
for {
mc <- merchantClients
r <- registry
if (r.alias == mc.alias && r.market == lift(market)
&& r.recordType == "M")
}
yield Client(mc.alias, mc.code, mc.orderPermission, mc.accountTag)
}
SELECT DISTINCT mc.alias, mc.code, order_permission, mc.account_tag
FROM MERCHANT_CLIENTS mc
JOIN REGISTRY r ON r.alias = mc.alias
WHERE r.market = 'us' AND r.record_type = 'M'
… with Case Classes it’s even better
28. def merchantClientsUdf(market:String):
Query[Quoted[(Option[String], Option[String], Option[Char], Option[String])]]
= quote {
for {
mc <- merchantClients
r <- registry
if (r.alias == mc.alias && r.market == lift(market)
&& r.recordType == "M")
} yield (mc.alias, mc.code, mc.orderPermission, mc.accountTag)
}
It’s a bit touchy with Optionals
29. … and for a reason
SELECT DISTINCT
mc.alias, mc.code, order_permission,
mc.account_tag
FROM MERCHANT_CLIENTS mc
JOIN REGISTRY r ON r.alias = mc.alias
30. select * from MERCHANT_CLIENTS
where ACCOUNT_TAG = null
select * from MERCHANT_CLIENTS
where ACCOUNT_TAG is null
Always False
Can be True
(null = null) = false
Says:
31. select * from MERCHANT_CLIENTS
where ACCOUNT_TAG = null
select * from MERCHANT_CLIENTS
where ACCOUNT_TAG is null
Always False
Can be True
(null = null) = false
32. select * from MERCHANT_CLIENTS
where ACCOUNT_TAG = null
select * from MERCHANT_CLIENTS
where ACCOUNT_TAG is null
Always False
Can be True
Can be True
(null = null) = true
SET ANSI_NULLS OFF
33. def merchantClientsUdf(market:String):
Query[Quoted[(Option[String], Option[String], Option[Char], Option[String])]]
= quote {
for {
mc <- mercantClients
r <- registry
if (r.alias.exists(rr == mc.alias.exists(_ == rr))
&& r.market.exists(_ == lift(market))
&& r.recordType.exists(_ == "M"))
} yield (mc.alias, mc.code, mc.orderPermission, mc.accountTag)
}
This will solve the problem…
40. SELECT DISTINCT
account.name, alias,
CASE (...) AS OFFICIAL_IDENTITY,
CASE (...)
FROM (...) client
INNER JOIN (
dbo.ACCOUNTS account
INNER JOIN ACCOUNT_TYPES accountType ON (account_type)
LEFT JOIN DEDICATED_ACCOUNTS dedicated ON (account_number)
)
ON (accountType.mapping_type = 0)
OR (possibly the account tag...)
OR (possibly the alias...)
name alias OFFICIAL_IDENTITY perm
TUNV FNF 111 ENH
TUNV ACME 111AC ENH
SIADV FNF 456 ENH
AUNV FNF 222 ENH
AUNV ACME 222AC ENH
ACMEINV ACME 808AC ENH
YOGADV YOGL 123 ST
TUNV YOGL 111 ST
AUNV YOGL 222 ST
41. SELECT DISTINCT
account.name, alias,
CASE (...) AS OFFICIAL_IDENTITY,
CASE (...)
FROM (...) client
INNER JOIN (
dbo.ACCOUNTS account
INNER JOIN ACCOUNT_TYPES accountType ON (account_type)
LEFT JOIN DEDICATED_ACCOUNTS dedicated ON (account_number)
)
ON (possibly anything...)
OR (accountType.mapping_type = 2 AND account.tag = client.account_tag)
OR (possibly the alias...)
name alias OFFICIAL_IDENTITY perm
TUNV FNF 111 ENH
TUNV ACME 111AC ENH
SIADV FNF 456 ENH
AUNV FNF 222 ENH
AUNV ACME 222AC ENH
ACMEINV ACME 808AC ENH
YOGADV YOGL 123 ST
TUNV YOGL 111 ST
AUNV YOGL 222 ST
42. SELECT DISTINCT
account.name, alias,
CASE (...) AS OFFICIAL_IDENTITY,
CASE (...)
FROM (...) client
INNER JOIN (
dbo.ACCOUNTS account
INNER JOIN ACCOUNT_TYPES accountType ON (account_type)
LEFT JOIN DEDICATED_ACCOUNTS dedicated ON (account_number)
)
ON (possibly anything...)
OR (possibly the account tag...)
OR (accountType.mapping_type = 1 AND dedicated.client_alias = client.alias)
name alias OFFICIAL_IDENTITY perm
TUNV FNF 111 ENH
TUNV ACME 111AC ENH
SIADV FNF 456 ENH
AUNV FNF 222 ENH
AUNV ACME 222AC ENH
ACMEINV ACME 808AC ENH
YOGADV YOGL 123 ST
TUNV YOGL 111 ST
AUNV YOGL 222 ST
50. SELECT
s189.s137, s189.s138, s189.s139, s189.s140, s176."NAME", s176."TAG", s176."NUMBER", s176."TYPE",
s177."ACCOUNT_TYPE", s177."MAPPING_TYPE", s47.s118, s47.s119, s47.s120
FROM "ACCOUNTS" s176
INNER JOIN "ACCOUNT_TYPES" s177
ON s176."TYPE" = s177."ACCOUNT_TYPE"
LEFT OUTER JOIN (
SELECT 1 AS s118, "ACCOUNT_NUMBER" AS s119, "CLIENT_ALIAS" AS s120
FROM "DEDICATED_ACCOUNTS") s47 ON s176."NUMBER" = s47.s119
INNER JOIN (
SELECT s179."ALIAS" AS s137, ? AS s138, s183."ORDER_PERMISSION" AS s139, s179."ACCOUNT_TAG" AS s140
FROM "SERVICE_CLIENTS" s179, "REGISTRY" s180, "PARTNERSHIPS" s183
WHERE ((((CASE WHEN (s179."ALIAS" IS NULL)
THEN ? ELSE cast(s179."ALIAS" AS VARCHAR(255)) END) = s180."ALIAS")
AND (s180."RECORD_TYPE" = 'M')) AND (s180."MARKET" = 'us')) AND (s183."ID" = s179."PARTNERSHIP_FK")
UNION ALL
SELECT s185."ALIAS" AS s137, s185."CODE" AS s138, s185."ORDER_PERMISSION" AS s139, s185."ACCOUNT_TAG" AS s140
FROM "MERCHANT_CLIENTS" s185, "REGISTRY" s186
WHERE (((CASE WHEN (s185."ALIAS" IS NULL)
THEN ? ELSE cast(s185."ALIAS" AS VARCHAR(255)) END) = s186."ALIAS") AND (s186."RECORD_TYPE" = 'M')) AND (s186."MARKET" = 'us'))
s189
ON (CASE WHEN ((CASE WHEN (s177."MAPPING_TYPE" IS NULL)
THEN ? ELSE cast(s177."MAPPING_TYPE" AS INTEGER) END) = 0)
THEN 1
WHEN (((CASE WHEN (s177."MAPPING_TYPE" IS NULL)
THEN ? ELSE cast(s177."MAPPING_TYPE" AS INTEGER) END) = 2) AND ((CASE WHEN (s176."TAG" IS NULL)
THEN ? ELSE cast(s176."TAG" AS VARCHAR(255)) END) = (CASE WHEN (s189.s140 IS NULL)
THEN ? ELSE cast(s189.s140 AS VARCHAR(255)) END)))
THEN 1
WHEN (((CASE WHEN (s177."MAPPING_TYPE" IS NULL)
THEN ? ELSE cast(s177."MAPPING_TYPE" AS INTEGER) END) = 1) AND ((CASE WHEN (s189.s137 IS NULL)
THEN ? ELSE cast(s189.s137 AS VARCHAR(255)) END) = (CASE WHEN ((CASE WHEN (s47.s118 IS NOT NULL)
THEN s47.s120 ELSE NULL END) IS NULL)
THEN ? ELSE cast((CASE WHEN (s47.s118 IS NOT NULL) THEN s47.s120
ELSE NULL END) AS VARCHAR(255)) END)))
THEN 1
ELSE 0 END) = 1
51. SELECT
s189.s137, s189.s138, s189.s139, s189.s140, s176."NAME", s176."TAG", s176."NUMBER", s176."TYPE",
s177."ACCOUNT_TYPE", s177."MAPPING_TYPE", s47.s118, s47.s119, s47.s120
FROM "ACCOUNTS" s176
INNER JOIN "ACCOUNT_TYPES" s177
ON s176."TYPE" = s177."ACCOUNT_TYPE"
LEFT OUTER JOIN (
SELECT 1 AS s118, "ACCOUNT_NUMBER" AS s119, "CLIENT_ALIAS" AS s120
FROM "DEDICATED_ACCOUNTS") s47 ON s176."NUMBER" = s47.s119
INNER JOIN (
SELECT s179."ALIAS" AS s137, ? AS s138, s183."ORDER_PERMISSION" AS s139, s179."ACCOUNT_TAG" AS s140
FROM "SERVICE_CLIENTS" s179, "REGISTRY" s180, "PARTNERSHIPS" s183
WHERE ((((CASE WHEN (s179."ALIAS" IS NULL)
THEN ? ELSE cast(s179."ALIAS" AS VARCHAR(255)) END) = s180."ALIAS")
AND (s180."RECORD_TYPE" = 'M')) AND (s180."MARKET" = 'us')) AND (s183."ID" = s179."PARTNERSHIP_FK")
UNION ALL
SELECT s185."ALIAS" AS s137, s185."CODE" AS s138, s185."ORDER_PERMISSION" AS s139, s185."ACCOUNT_TAG" AS s140
FROM "MERCHANT_CLIENTS" s185, "REGISTRY" s186
WHERE (((CASE WHEN (s185."ALIAS" IS NULL)
THEN ? ELSE cast(s185."ALIAS" AS VARCHAR(255)) END) = s186."ALIAS") AND (s186."RECORD_TYPE" = 'M')) AND (s186."MARKET" = 'us'))
s189
ON (CASE WHEN ((CASE WHEN (s177."MAPPING_TYPE" IS NULL)
THEN ? ELSE cast(s177."MAPPING_TYPE" AS INTEGER) END) = 0)
THEN 1
WHEN (((CASE WHEN (s177."MAPPING_TYPE" IS NULL)
THEN ? ELSE cast(s177."MAPPING_TYPE" AS INTEGER) END) = 2) AND ((CASE WHEN (s176."TAG" IS NULL)
THEN ? ELSE cast(s176."TAG" AS VARCHAR(255)) END) = (CASE WHEN (s189.s140 IS NULL)
THEN ? ELSE cast(s189.s140 AS VARCHAR(255)) END)))
THEN 1
WHEN (((CASE WHEN (s177."MAPPING_TYPE" IS NULL)
THEN ? ELSE cast(s177."MAPPING_TYPE" AS INTEGER) END) = 1) AND ((CASE WHEN (s189.s137 IS NULL)
THEN ? ELSE cast(s189.s137 AS VARCHAR(255)) END) = (CASE WHEN ((CASE WHEN (s47.s118 IS NOT NULL)
THEN s47.s120 ELSE NULL END) IS NULL)
THEN ? ELSE cast((CASE WHEN (s47.s118 IS NOT NULL) THEN s47.s120
ELSE NULL END) AS VARCHAR(255)) END)))
THEN 1
ELSE 0 END) = 1
52. SELECT
client.other_alias, client.code, client.order_permission, client.account_tag,
account_type.name, account_type.tag, account_type.number, account_type.type, account_type.account_type,
account_type.mapping_type,
x11.account_number, x11.client_alias
FROM (SELECT
account.type type, account.name name, account.number number,
account.tag tag, account_type.account_type account_type, account_type.mapping_type mapping_type
FROM accounts account, account_types account_type
WHERE account.type = account_type.account_type) account_type
LEFT JOIN dedicated_accounts x11
ON x11.account_number = account_type.number,
(
(SELECT sc.account_tag account_tag, sc.alias other_alias, ? code, part.order_permission order_permission
FROM service_clients sc, registry r, partnerships part
WHERE sc.alias = r.alias AND r.market = ? AND r.record_type = 'S' AND sc.partnership_fk = part.id)
UNION ALL
(SELECT mc.account_tag account_tag, mc.alias other_alias, mc.code code, mc.order_permission order_permission
FROM merchant_clients mc, registry r1
WHERE mc.alias = r1.alias AND r1.market = ? AND r1.record_type = 'M')
) client
WHERE CASE WHEN CASE WHEN account_type.mapping_type IS NOT NULL
THEN ? ELSE 0 END = 0
THEN 1
WHEN CASE WHEN account_type.mapping_type IS NOT NULL
THEN ?
ELSE 0 END = 2 AND client.account_tag = CASE WHEN account_type.tag IS NOT NULL
THEN ? ELSE '' END
THEN 1
WHEN CASE WHEN account_type.mapping_type IS NOT NULL
THEN ?
ELSE 0 END = 1 AND client.other_alias = x11.client_alias
THEN 1
ELSE 0 END = 1
53. SELECT
client.other_alias, client.code, client.order_permission, client.account_tag,
account_type.name, account_type.tag, account_type.number, account_type.type, account_type.account_type,
account_type.mapping_type,
x11.account_number, x11.client_alias
FROM (SELECT
account.type type, account.name name, account.number number,
account.tag tag, account_type.account_type account_type, account_type.mapping_type mapping_type
FROM accounts account, account_types account_type
WHERE account.type = account_type.account_type) account_type
LEFT JOIN dedicated_accounts x11
ON x11.account_number = account_type.number,
(
(SELECT sc.account_tag account_tag, sc.alias other_alias, ? code, part.order_permission order_permission
FROM service_clients sc, registry r, partnerships part
WHERE sc.alias = r.alias AND r.market = ? AND r.record_type = 'S' AND sc.partnership_fk = part.id)
UNION ALL
(SELECT mc.account_tag account_tag, mc.alias other_alias, mc.code code, mc.order_permission order_permission
FROM merchant_clients mc, registry r1
WHERE mc.alias = r1.alias AND r1.market = ? AND r1.record_type = 'M')
) client
WHERE CASE WHEN CASE WHEN account_type.mapping_type IS NOT NULL
THEN ? ELSE 0 END = 0
THEN 1
WHEN CASE WHEN account_type.mapping_type IS NOT NULL
THEN ?
ELSE 0 END = 2 AND client.account_tag = CASE WHEN account_type.tag IS NOT NULL
THEN ? ELSE '' END
THEN 1
WHEN CASE WHEN account_type.mapping_type IS NOT NULL
THEN ?
ELSE 0 END = 1 AND client.other_alias = x11.client_alias
THEN 1
ELSE 0 END = 1
99. We require that each query in the host language generate
exactly one SQL query. Alluding to twin perils Odysseus
sought to skirt when navigating the straits of Medina, we
seek to avoid Scylla and Charybdis. Scylla stands for the case
where the system fails to generate a query, signalling an error.
Charybdis stands for the case where the system generates
multiple queries, hindering efficiency. The overhead of
accessing a database is high, and to a first approximation cost
is proportional to the number of queries. We particularly want
to avoid a query avalanche, in the sense of Grust et al.
(2010), where a single host query generates a number of SQL
queries proportional to the size of the data
100. Our work avoids these perils. For T-LINQ, we prove the
Scylla and Charybdis theorem, characterising when a host
query is guaranteed to generate a single SQL query. All our
examples are easily seen to satisfy the characterisation in the
theorem, and indeed our theory yields the same SQL query
for each that one would write by hand. For P-LINQ, we
verify that its run time on our examples is comparable to that
of F# 2.0 and F# 3.0, in the cases where those systems
generate a query, and significantly faster in the one case
where F# 3.0 generates an avalanche—indeed, arbitrarily
faster as the size of the data grows.
101. Our work avoids these perils. For T-LINQ, we prove the
Scylla and Charybdis theorem, characterising when a host
query is guaranteed to generate a single SQL query. All our
examples are easily seen to satisfy the characterisation in the
theorem, and indeed our theory yields the same SQL query
for each that one would write by hand. For P-LINQ, we
verify that its run time on our examples is comparable to that
of F# 2.0 and F# 3.0, in the cases where those systems
generate a query, and significantly faster in the one case
where F# 3.0 generates an avalanche—indeed, arbitrarily
faster as the size of the data grows.
120. Structural Type-Based Extensions
case class ShippingOrder(customerId:Int, startedAt:LocalDateTime, endedAt:LocalDateTime)
case class ProcessRequest(regionId:Int, startedAt:LocalDateTime, endedAt:LocalDateTime)
case class Event(typeCode:String, startedAt:LocalDateTime, endedAt:LocalDateTime)
121. Structural Type-Based Extensions
case class ShippingOrder(customerId:Int, startedAt:LocalDateTime, endedAt:LocalDateTime)
case class ProcessRequest(regionId:Int, startedAt:LocalDateTime, endedAt:LocalDateTime)
case class Event(typeCode:String, startedAt:LocalDateTime, endedAt:LocalDateTime)
implicit class TemporalObjectExtensions[T <: { def startedAt: LocalDateTime; def endedAt: LocalDateTime }](
records:Query[T])
{
def existedAt(date: LocalDateTime) =
quote {
records.filter(r => (lift(r.startedAt) < date) && lift(r.endedAt) > date))
}
}
val q = quote { query[ShippingOrder].existedAt(lift(now)) }
val q = quote { query[ProcessRequest].existedAt(lift(now)) }
val q = quote { query[Event].existedAt(lift(now)) }
122. No Infix Date Ops?
case class ShippingOrder(customerId:Int, startedAt:LocalDateTime, endedAt:LocalDateTime)
case class ProcessRequest(regionId:Int, startedAt:LocalDateTime, endedAt:LocalDateTime)
case class Event(typeCode:String, startedAt:LocalDateTime, endedAt:LocalDateTime)
implicit class TemporalObjectExtensions[T <: { def startedAt: LocalDateTime; def endedAt: LocalDateTime }](
records:Query[T])
{
def existedAt(date: LocalDateTime) =
quote {
records.filter(r => (lift(r.startedAt) < date) && lift(r.endedAt) > date))
}
}
val q = quote { query[ShippingOrder].existedAt(lift(now)) }
val q = quote { query[ProcessRequest].existedAt(lift(now)) }
val q = quote { query[Event].existedAt(lift(now)) }
131. What if we can test Queries…
class MyMemoryDriver extends ModifiedMemoryProfile { }
class MyHeapDriver extends RelationalTestDB {
type Driver = MemoryDriver
val driver: Driver = new MyMemoryDriver
}
132. Without a Database!
class AccountClientOrderMerchantSupplierTest extends FunSuite with BeforeAndAfter {
val heapDriver = new MyHeapDriver
val profile = heapDriver.driver.profile
import profile.api._
before { initializeEntireSchema() }
test("Create Accounts, Clients, Orders, Merchants, Suppliers and Test") {
db.run(for {
_ <- accounts ++= Seq(Account(...), ...)
_ <- clients ++= Seq(Client(...), ...)
_ <- orders ++= Seq(Order(...), ...)
_ <- merchants ++= Seq(Merchant(...), ...)
_ <- suppliers ++= Seq(Supplier(...), ...)
q <- giantQueryThatCombinesEverything()
_ = { assertRealityIsInLineWithExpectations(accounts, clients, orders, merchants, suppliers) }
} yield ())
}
133. Without a Database!
• Create Schema (~100 `Tables`)
• Initialize Schema with Dozens of Records
• Run Integration Tests with Real Production Data
• 200 ~ 400ms Per Test
141. DataFrame API
orders.as("o")
// Customer has a Location
.join(customers.as("c"), customers("id") === orders("customer"))
.join(destinations.as("d"), destinations("id") === customers("destination"))
// Supplier has a destination
.join(suppliers.as("s"), suppliers("id") === orders("supplier"))
.join(warehouses.as("w"), warehouses("supplier") === suppliers("id"))
.where(warehouses("address") === destinations("address"))
.select(
$"o.timePlaced",
$"c.firstName", $"c.LastName",
$"w.address",
$"d.address"
142. Spark 1.6.x Version
The Spark Dataset API brings the best of RDD and Data
Frames together, for type safety and user functions that run
directly on existing JVM types.
A Dataset is a strongly-typed, immutable collection
of objects that are mapped to a relational schema.
143. Dataset API
orders.as("o")
// Customer has a Location
.join(customers.as("c"), customers("id") === orders("customer"))
.join(destinations.as("d"), destinations("id") === customers("destination"))
// Supplier has a destination
.join(suppliers.as("s"), suppliers("id") === orders("supplier"))
.join(warehouses.as("w"), warehouses("supplier") === suppliers("id"))
.where(warehouses("address") === destinations("address"))
.select(
$"o.timePlaced",
$"c.firstName", $"c.LastName",
$"w.address",
$"d.address"
144. Back to the Trenches?
select
o.timePlaced,
c.firstName, c.LastName,
w.address,
d.address
from
orders o
join customers c on c.id = o.customer
join destinations d on d.id = c.destination
join suppliers s on s.id = o.supplier
join warehouses w on w.supplier = s.id
where
warehouses.address = destinations.address
145. Salvation Cometh... in the form of a QuillSparkContext
def sameAreaOrder = quote {
for {
o <- orders
c <- customers if (c.id === o.customer)
d <- destinations if (d.id === c.destination)
s <- suppliers if (s.id === o.supplier)
w <- warehouses if (w.supplier === s.id)
} yield (
o.timePlaced,
c.firstName, c.LastName,
w.address,
d.address
)
}
146. def sameAreaOrder = quote {
for {
o <- query[Orders]
c <- query[Customers] if (c.id === o.customer)
d <- query[Destinations] if (d.id === c.destination)
s <- query[Suppliers] if (s.id === o.supplier)
w <- query[Warehouses] if (w.supplier === s.id)
} yield (
o.timePlaced,
c.firstName, c.LastName,
w.address,
d.address
)
}
Salvation Cometh... in the form of a QuillSparkContext
Stuff that must be done in database schemas, or spark dataframes. Future like past sto
EU_Client_accounts almost all technical debt, pretend inline udf exists that can return parameterized views
EU_Client_accounts almost all technical debt
EU_Client_accounts almost all technical debt
Whenever introduce new business unit, copy almost all code
Last time I mentioned Quill doesn’t support CC yields, Brian asked how to do >22 arity….
Quill always had a ‘Unlimited Tuple type’
Added CC in yields as of 2.2.0 now it’s on 2.3.1
Note, that union operator is possible because same type is returned
Note, that union operator is possible because same type is returned
Option.flatten and Option.flatMap doesn’t exist.
Have to use the pattern for val/def myMethod = quote { (a,b,c) => output } for passing individual fields
Slick API more closely resuembles scala collections (sto), quill tries to be more natural to the use case. ONLY ALTERNATIVE TO batch inserts in thousands and then using them for other inserts. I.e. use those ids to create foreign keys in another column in another table.
Paper published 2013
sto
Russian phrase, system that has multiple levels of self delusion, sto, fractally self deleted
Paper published 2013
2015
Note that upper query cannot be generated at compile time
I.e. these classes were produced by a code generator, don’t always know which generator outputs will have them or not (because these are parquet files in a data lake)
Half hour test suite vs half minute test suite. Can test an ETL system this way simply
Sto, they all implemented SQL? Why? Is there really nothing better?
Sto, they all implemented SQL? Why? Is there really nothing better?