48
Расширение библиотеки Slick Slick – библиотека для работы с БД на Scala Арсений Жижелев

Расширение библиотеки Slick

  • Upload
    -

  • View
    160

  • Download
    1

Embed Size (px)

Citation preview

Page 1: Расширение библиотеки Slick

Расширение библиотеки SlickSlick – библиотека для работы с БД на Scala

Арсений Жижелев

Page 2: Расширение библиотеки Slick

2

1. ВВЕДЕНИЕ SLICK

Небольшое введение в работу с БД с помощью библиотеки Slick

Page 3: Расширение библиотеки Slick

3

Основные возможности Схема БД на Scala

◦ Стандартные и пользовательские типы◦ Первичные и внешние ключи◦ Индексы◦ Ограничения

Текстовый SQL (sql"SELECT * FROM users") DSL, напоминающий коллекции

◦ Select, where◦ Join (включая авто-join по foreign key)◦ Update, Delete (отобранных записей)◦ Insert (включая server-side, bulk insert)

Поддержка всех основных СУБД Расширяемая архитектура

Page 4: Расширение библиотеки Slick

4

Схема

class Suppliers(tag: Tag) extends Table[(Int, String, String)](tag, "SUPPLIERS") { def id = column[Int]("SUP_ID", O.PrimaryKey, O.AutoInc) def name = column[String]("NAME") def city = column[String]("CITY") def * = (id, name, city)}

val suppliers = TableQuery[Suppliers]

http://slick.typesafe.com/talks/2014-09-24_ScalaCamp/Introduction_to_Slick_2.1_and_2.2.pdf

Page 5: Расширение библиотеки Slick

5

Подключение к БД

import scala.slick.driver.H2Driver.simple._

val db = Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver")

db.withSession { implicit session => // suppliers.ddl.create // – создание схемы // Use the session: val result = myQuery.run}

Page 6: Расширение библиотеки Slick

6

Структура запроса

Page 7: Расширение библиотеки Slick

7

Простые текстовые запросы

def personsMatching(pattern: String)(conn: Connection) = { val st = conn.prepareStatement( "SELECT id, name FROM person WHERE name LIKE ?") try { st.setString(1, pattern) val rs = st.executeQuery() try { val b = new ListBuffer[(Int, String)] while(rs.next) b.append((rs.getInt(1), rs.getString(2))) b.toList } finally rs.close() } finally st.close()}

JDBC

Page 8: Расширение библиотеки Slick

8

Простые текстовые запросы

def personsMatching(pattern: String)(implicit s: Session) =

sql"SELECT id, name FROM person WHERE name LIKE $pattern“ .as[(Int, String)].list

Sql-interpolation

Page 9: Расширение библиотеки Slick

9

Поддерживаемые СУБД

SlickPostgreSQLMySQLH2HsqldbDerby / JavaDBSQLiteAccess

Slick Extension$OracleDB2SQL Server

Page 10: Расширение библиотеки Slick

10

СсылкиStefan Zeiger 2014-09-24

Introduction to Slick 2.1 and 2.2 / ScalaCamp #7, Kraków, Poland (http://slick.typesafe.com/talks/2014-09-24_ScalaCamp/Introduction_to_Slick_2.1_and_2.2.pdf)

И др. на странице Slick@Typesafe http://slick.typesafe.com/docs/

Page 11: Расширение библиотеки Slick

11

2. АРХИТЕКТУРА SLICK

Послойное рассмотрение архитектуры Slick

Page 12: Расширение библиотеки Slick

12

КомпонентыПользовательский уровеньЗапросы

◦ Lifted (DSL для формирования запросов)◦ Direct (на макросах)◦ SQL-interpolation - текстовые запросы

Схема БД: Table и TableQueryСистемный уровеньAST – синтаксическое дерево будущего

запросаQueryCompiler – компилятор запросовRuntimeDriverSession management – подключение к БД

Page 13: Расширение библиотеки Slick

13

2.0. КОНЦЕПЦИЯComprehension

Page 14: Расширение библиотеки Slick

14

SQL ~ functional

Relational ModelRelationAttributeTupleRelation ValueRelation Variable

http://slick.typesafe.com/talks/2014-09-24_ScalaCamp/Introduction_to_Slick_2.1_and_2.2.pdf

Page 15: Расширение библиотеки Slick

15

SQL ~ functional

Relational ModelRelationAttributeTupleRelation ValueRelation Variable

http://slick.typesafe.com/talks/2014-09-24_ScalaCamp/Introduction_to_Slick_2.1_and_2.2.pdf

case class Coffee( name: String, supplierId: Int, price: Double)

val coffees = Set( Coffee("Colombian", 101, 7.99), Coffee("French_Roast", 49, 8.99), Coffee("Espresso", 150, 9.99))

Page 16: Расширение библиотеки Slick

16

Set-comprehensionКонструктор множества

◦Исходное множество – генератор◦Фильтр◦Отображение

712,|2 xxx

for { x <- 1 to 10 if 2*x+1<7} yield math.pow(x,2)

SELECT x^2 FROM sequence(1,10) WHERE 2*x+1<7

Page 17: Расширение библиотеки Slick

17

2.1. AST

Структура запросов AST – abstract syntax tree (forest)

Page 18: Расширение библиотеки Slick

18

AST: Node Node

◦ SimpleExpression – простой SQL ◦ SimpleFunction ◦ TableNode, TableExpansion – таблица ◦ …

mix-in’s◦ DefNode◦ TypedNode (обычно Rep[T])◦ SimplyTypedNode (имеет непосредственный тип)

N-кратные узлы◦ NullaryNode◦ UnaryNode◦ BinaryNode

Comprehension – полное выражение select, включающее источник, фильтры, порядок, группировку, список колонок, смещение и размер страницы

Insert – выражение insert (update и delete выражаются через comprehension)

Page 19: Расширение библиотеки Slick

19

AST: SymbolSymbol – представляет имена в

запросе◦Library – объект, содержащий

экземпляры Symbol для стандартных операторов и функций

◦AnonSymbol – уникальное имяDefNode – узел, вводящий

новое имя

Page 20: Расширение библиотеки Slick

20

AST: TypeAtomicType (простой тип)ProductType (кортеж)StructType («запись» с полями)CollectionType (тип коллекции, на

основе CanBuild)MappedScalaType (содержит

двусторонний конвертер)

implicit TypedType[T] – почти везде

Page 21: Расширение библиотеки Slick

21

2.2. LIFTED (DSL)

Язык построения запросов на основе Rep[T], for-comprehension и множества implicit’ов, которые конструируют AST

Page 22: Расширение библиотеки Slick

22

Rep[T]Column[T] – методы расширения для

колонок разных типов

Query[T] – методы, оперирующие запросами◦for-comprehension (map, flatMap, filter)◦ join’ы, zip’ы,◦sort, group by, order by, union, ◦ take (aka limit), drop (aka offset)

MappedProjection – client-side конвертация в пользовательские классы

Page 23: Расширение библиотеки Slick

23

Shape Конструируемый запрос

Query[T, RepT, _] T и RepT – сильно связаны

implicit Shape[Level, _, T, RepT] primitiveShape RepShape

◦ optionShape ProductNodeShape

◦ TupleShape (tuple1..tuple22 shapes)◦ MappedProductShape◦ MappedScalaProductShape

HListShape CaseClassShape

Page 24: Расширение библиотеки Slick

24

Nullable колонки (1):OptionMapperDSLB1 – базовый тип T, P1 –

базовый T или Option[T]object OptionMapperDSL { type arg[B1, P1] = { type to[BR, PR] = OptionMapper2[B1, B1, BR, P1, P1, PR] type toSame = OptionMapper2[B1, B1, B1, P1, P1, P1] type arg[B2, P2] = { type to[BR, PR] = OptionMapper2[B1, B2, BR, P1, P2, PR] type arg[B3, P3] = { type to[BR, PR] = OptionMapper3[B1, B2, B3, BR, P1, P2, P3, PR] } } }}

val om = OptionMapperDSL

om#arg[B1,P1]#arg[B2,P2]#to[BR,PR]

Page 25: Расширение библиотеки Slick

25

Nullable колонки (2)Тип результата – Option, если

хотя бы один аргумент – Option. Иначе – базовый тип

trait ColumnExtensionMethods[B1, P1] extends Any with ExtensionMethods[B1, P1] {

protected[this] def c: Column[P1]

def === [P2, R](e: Column[P2])(implicit om: o#arg[B1, P2]#to[Boolean, R]) = om.column(Library.==, n, e.toNode)

}

Page 26: Расширение библиотеки Slick

26

Abstract table, TagAbstractTable

◦Объявления колонок, индексов, foreignKey, primaryKey

◦def * Источник колонок для DDL ProvenShape (увязывает колонки в record

type, Shape и конвертер <>)

Tag – связь с AST (цепочка переходов, приводящая к текущей таблице)◦BaseTag – сама таблица◦RefTag – узел AST, имеющий тип таблицы

Page 27: Расширение библиотеки Slick

27

2.3. ДРАЙВЕР

Все возможности Slick представлены через API, реализуемое драйвером БД

Page 28: Расширение библиотеки Slick

28

Cake pattern для драйвераComponent

◦ BasicInvokerComponent, BasicInsertInvokerComponent, BasicExecutorComponent, BasicActionComponent

◦ RelationalTableComponent, RelationalSequenceComponent, RelationalTypesComponent, RelationalActionComponent

Profile (наследуется от профиля + несколько компонентов)

Driver (реализует профиль, возможно, путём подмешивания компонентов с реализацией)

Page 29: Расширение библиотеки Slick

29

Драйвер Postgrestrait BasicProfile extends BasicInvokerComponent with BasicInsertInvokerComponent with BasicExecutorComponent with BasicActionComponent

trait RelationalProfile extends BasicProfile with RelationalTableComponent with RelationalSequenceComponent with RelationalTypesComponent with RelationalActionComponent

trait SqlProfile extends RelationalProfile with SqlExecutorComponent with SqlTableComponent with SqlActionComponent

trait BasicDriver extends BasicProfile

trait RelationalDriver extends BasicDriver with RelationalProfile

trait SqlDriver extends RelationalDriver with SqlProfile with SqlUtilsComponent

trait JdbcDriver extends SqlDriver with JdbcProfile with JdbcStatementBuilderComponent with JdbcMappingCompilerComponent

trait PostgresDriver extends JdbcDriver

Page 30: Расширение библиотеки Slick

30

Драйвер: SimpleQLКаждый компонент может

перекрыть SimpleQL и добавить элементы в user-scope (import …Driver.simple._)

trait SomeComponent extends BasicProfile { override val simple: SimpleQL = new SimpleQL {} trait SimpleQL extends super.SimpleQL { type MyType = MyClass val someVal def doSomething } class MyClass }

Page 31: Расширение библиотеки Slick

31

Драйвер: capabilityВозможности драйвера

реализуются и декларируются компонентами

object RelationalProfile { object capabilities { /** Supports default values in column definitions */ val columnDefaults = Capability("relational.columnDefaults") … val all = Set(columnDefaults) }}

trait ColumnDefaultsComponent { override protected def computeCapabilities = super. computeCapabilities ++ capabilities.all}

Page 32: Расширение библиотеки Slick

32

2.4. КОМПИЛЯЦИЯКомпилятор запросов

Page 33: Расширение библиотеки Slick

33

Преобразование запроса

Lifted Embedding Direct Embedding

Slick AST

Scala ASTScala

Compiler

Slick Macros

Slick AST

Query Compiler

ResultExecutor

DB

Page 34: Расширение библиотеки Slick

34

Фазы компиляции(state => state)/rewriteCleanUp inline assignUniqueSymbols expandTables inferTypes createResultSetMappings forceOuterBindsFlattenColumns expandRefs replaceFieldSymbols rewritePaths relabelUnions pruneFields assignTypes

SQL Shape (not in memory driver)

resolveZipJoins convertToComprehe

nsions fuseComprehensions fixRowNumberOrderi

ng hoistClientOpsGenerate Code codeGen (driver

specific)

Page 35: Расширение библиотеки Slick

35

3. РАСШИРЕНИЕ SLICK

Встроенная возможность расширения функциональности Slick

Page 36: Расширение библиотеки Slick

36

Механизмы расширенияБазовые возможности

◦Пользовательские функции в БД◦Простые типы-обёртки (MyId)◦Пользовательские типы результатов

(MappedProjection <>)◦Композитные типы (PgComposite)

Расширения в драйвере◦ColumnOption – метаинформация к колонкам◦TableDDLBuilder – создание таблиц

Расширения в компиляторе◦Новые типы узлов, фазы rewrite, генерация

SQL

Page 37: Расширение библиотеки Slick

37

Пользовательские функцииtrait AgeFunctionTrait extends DatabaseSchema { val getAgeFName = "get_age"

override val ddl = super.ddl ++ DDL(List(s""" |CREATE FUNCTION $getAgeFName(date_of_birth DATE) | RETURN NUMBER |AS BEGIN | RETURN | TRUNC(MONTHS_BETWEEN(SYSDATE, date_of_birth)/12); |END; """.stripMargin ), List(), List(s"DROP FUNCTION $getAgeFName"), List()) val getAge = SimpleFunction.unary[Date, Int](getAgeFName)}

Page 38: Расширение библиотеки Slick

38

Пользовательские функции (2)val ageHistogram = for { (age, q) <- persons .map(p => (getAge(p.dateOfBirth), p.id)) .groupBy(_._1)} yield (age, q.size)

Page 39: Расширение библиотеки Slick

39

Простые типы-обёрткиcase class MyID(value: Long) extends MappedTo[Long]

class MyTable(tag: Tag) extends Table[(MyID, String)](tag, "MY_TABLE") { def id = column[MyID]("ID") def data = column[String]("DATA") def * = (id, data)}

Page 40: Расширение библиотеки Slick

40

Пользовательские типы (<>)trait PersonSchema extends DatabaseSchema { val personTableName = "person"

case class Person(id:Int, name:String, dateOfBirth:Option[Timestamp])

class PersonTable(tag:Tag) extends Table[Person](tag, personTableName){

def id = column[Int] ("id", O.AutoInc) def name = column[String]("name") def dateOfBirth = column[Option[Timestamp]] ("date_of_birth") def * = (id, name, dateOfBirth) <> (Person.tupled, Person.unapply) } val person = TableQuery[PersonTable]}

Page 41: Расширение библиотеки Slick

41

Композитные типы (SlickPg)trait MyPointTrait extends DatabaseSchema { type Driver <: PgCompositeSupport

val myPointTName = "my_point"

override val ddl = super.ddl ++ DDL(List( s"CREATE TYPE $myPointTName(x int, y int)" ), List(), List(s"DROP TYPE $myPointTName"), List()) case class MyPoint(x: Int, y:Int)

implicit val myPointTypeMapper = createCompositeJdbcType[MyPoint](myPointTName)}

Page 42: Расширение библиотеки Slick

42

Slick-pgТипы Array Date/Time (включая Joda, ThreeTen) Range Enum Json (включая Play Json) HStore, LTree Text (full text search – tsquery, tsvector) PostGis geometry Inet/MacAddrВозможности Postgres CompositeType Наследование

Page 43: Расширение библиотеки Slick

43

Расширение драйвера (1)

import slick.driver.PostgresDriverimport com.github.tminglei.slickpg._

trait MyPostgresDriver extends PostgresDriver with PgCompositeSupport { override lazy val Implicit = new Implicits {} override val simple = new SimpleQL {}

trait Implicits extends super.Implicits with MyImplicits

trait SimpleQL extends super.SimpleQL with Implicits with MyQL trait MyQL { def myFavouriteMethod }}object MyPostgresDriver extends MyPostgresDriver

Page 44: Расширение библиотеки Slick

44

Расширение драйвера (2)

trait MyColumnOptComponent { driver : JdbcPostgresDriver => trait ColumnOptions extends super.ColumnOptions { def MyMetaData (data: String) = MyColumnOptions.MyMetaData(data) }

override val columnOptions: ColumnOptions = new ColumnOptions {}}

object MyColumnOptions { case class MyMetaData(data:String) extends ColumnOption[Nothing] //Для колонок любого типа}// abstract class ColumnOption[+T]

Page 45: Расширение библиотеки Slick

45

Расширение драйвера (3)

trait MyTableComponent { driver : JdbcPostgresDriver =>

override def createTableDDLBuilder(table: Table[_]) = new TableDDLBuilder(table) override def createColumnDDLBuilder(column: FieldSymbol, table: Table[_]) = new ColumnDDLBuilder(column)

class TableDDLBuilder(table: Table[_]) extends super.TableDDLBuilder(table) { override def createPhase1 = super.createPhase1.mkString(""). replaceAll("CREATE TABLE", "CREATE TEMPORARY TABLE") }}

object MyPostgresDriver extends MyPostgresDriver with MyTableComponent

Page 46: Расширение библиотеки Slick

46

Генерация SQL (ILIKE)trait ILikeComponent { driver : JdbcPostgresDriver => override def createQueryBuilder(n: Node, state: CompilerState) = new QueryBuilder(n, state) class QueryBuilder(tree: Node, state: CompilerState) extends super. QueryBuilder(tree, state) { override def expr(n: Node, skipParens: Boolean = false) = n match { case Library.ILike(l, r) => b"\($l ilike $r\)“ case _ => super.expr(n, skipParens) } }}final class ILikeStringColumnExtensionMethods[P1](val c: Column[P1]) extends AnyVal with ExtensionMethods[String, P1] { def ilike[P2, R](e: Column[P2], esc: Char = '\u0000')(implicit om: o#arg[String, P2]#to[Boolean, R]) = if(esc == '\u0000') om.column(Library.ILike, n, e.toNode) else om.column(Library.ILike, n, e.toNode, LiteralNode(esc))}object Library { val ILike = new FunctionSymbol(“ILike")}

Page 47: Расширение библиотеки Slick

47

Ключевые особенности SlickFunctional Relational MappingЦелостная архитектураМодульность и расширяемостьНовый строго типизированный

язык запросов (по-видимому, эквивалентный по выразительности SQL)

http://slick.typesafe.com/https://github.com/slick/slick/

Page 48: Расширение библиотеки Slick

48

WelcomeSlickhttp://slick.typesafe.com/https://github.com/slick/slick/

SynapseGridhttps://github.com/Primetalk/SynapseGrid/

'ru.primetalk:synapse-grid-core_2.10:1.3.5'

Open source - BSD

Жижелев Арсений[email protected]

https://github.com/Primetalk/SynapseGrid/