Upload
-
View
160
Download
1
Embed Size (px)
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
Жижелев Арсений[email protected]
https://github.com/Primetalk/SynapseGrid/