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

Preview:

Citation preview

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

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

2

1. ВВЕДЕНИЕ SLICK

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

3

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

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

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

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

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

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

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}

6

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

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

8

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

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

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

Sql-interpolation

9

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

SlickPostgreSQLMySQLH2HsqldbDerby / JavaDBSQLiteAccess

Slick Extension$OracleDB2SQL Server

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/

11

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

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

12

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

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

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

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

13

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

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

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))

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

17

2.1. AST

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

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)

19

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

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

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

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

новое имя

20

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

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

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

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

21

2.2. LIFTED (DSL)

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

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 конвертация в пользовательские классы

23

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

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

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

◦ optionShape ProductNodeShape

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

HListShape CaseClassShape

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]

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)

}

26

Abstract table, TagAbstractTable

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

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

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

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

27

2.3. ДРАЙВЕР

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

28

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

◦ BasicInvokerComponent, BasicInsertInvokerComponent, BasicExecutorComponent, BasicActionComponent

◦ RelationalTableComponent, RelationalSequenceComponent, RelationalTypesComponent, RelationalActionComponent

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

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

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

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 }

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}

32

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

33

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

Lifted Embedding Direct Embedding

Slick AST

Scala ASTScala

Compiler

Slick Macros

Slick AST

Query Compiler

ResultExecutor

DB

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)

35

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

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

36

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

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

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

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

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

SQL

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)}

38

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

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)}

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]}

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)}

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 Наследование

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

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]

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

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")}

47

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

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

http://slick.typesafe.com/https://github.com/slick/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

Жижелев Арсенийzhizhelev@primetalk.ru

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

Recommended