├── .gitignore
├── .travis.yml
├── LICENSE
├── build.sbt
├── codecov.yml
├── project
├── SqlzUtil.scala
├── build.properties
└── plugins.sbt
├── readme.md
├── scripts
├── build.sh
└── publishSite.sh
├── sqlz-algebra
└── ansi
│ └── src
│ └── main
│ └── scala
│ └── sqlz
│ └── algebra
│ └── ansi
│ ├── Newtypes.scala
│ └── jdbc.scala
├── sqlz-tagless
└── ansi
│ └── src
│ └── main
│ └── scala
│ └── sqlz
│ └── tagless
│ ├── PreparableSql.scala
│ └── ansi.scala
└── version.sbt
/.gitignore:
--------------------------------------------------------------------------------
1 | *.class
2 | *.log
3 | .idea/
4 | project/project/
5 |
6 | # sbt specific
7 | .cache
8 | .history
9 | .lib/
10 | dist/*
11 | target*/
12 | lib_managed/
13 | src_managed/
14 | project/boot/
15 | project/plugins/project/
16 |
17 | # Scala-IDE specific
18 | .scala_dependencies
19 | .worksheet
20 |
21 | .ensime*
22 | *.log*
23 | *.sc
24 | tags
25 | .dummy/
26 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: scala
2 |
3 | jdk:
4 | - oraclejdk8
5 |
6 | scala:
7 | - 2.12.2
8 | - 2.11.11
9 |
10 | script:
11 | - bash scripts/build.sh $TEST_SUITE $ACTION $AND
12 |
13 | addons:
14 | postgresql: "9.3"
15 | mysql: "5.7"
16 | apt:
17 | packages:
18 | - postgresql-9.3-postgis-2.3
19 |
20 | after_success:
21 | - if [ "$TRAVIS_SCALA_VERSION" = "2.12.2" ]; then bash <(curl -s https://codecov.io/bash); fi
22 | - if [ "$TRAVIS_PULL_REQUEST" = "true" ]; then echo "Not in master branch, skipping deploy and release"; fi
23 |
24 | cache:
25 | directories:
26 | - $HOME/.sbt/0.13/dependency
27 | - $HOME/.sbt/boot/scala*
28 | - $HOME/.sbt/launchers
29 | - $HOME/.ivy2/cache
30 |
31 | before_cache:
32 | - du -h -d 1 $HOME/.ivy2/cache
33 | - du -h -d 2 $HOME/.sbt/
34 | - find $HOME/.sbt -name "*.lock" -type f -delete
35 | - find $HOME/.ivy2/cache -name "ivydata-*.properties" -type f -delete
36 |
37 | notifications:
38 | webhooks:
39 | urls:
40 | - https://webhooks.gitter.im/e/e7f5152049eff61e3376
41 | on_success: change # options: [always|never|change] default: always
42 | on_failure: always # options: [always|never|change] default: always
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016-2017 Jacob Barber
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | import ReleaseTransformations._
2 | import SqlzUtil._
3 |
4 |
5 | lazy val sqlz =
6 | project.in(file("./.root"))
7 | .configs(IntegrationTest)
8 | .settings(Defaults.itSettings)
9 | .settings(name := "sqlz")
10 | .settings(sqlzSettings ++ noPublishSettings)
11 | .dependsOn(ansiTagless, ansiAlgebra, data)
12 | .aggregate(ansiTagless, ansiAlgebra, data)
13 | .settings(
14 | publishAllSigned :=
15 | Def.sequential(
16 | PgpKeys.publishSigned in ansiTagless,
17 | PgpKeys.publishSigned in data
18 | ).value
19 | )
20 |
21 | lazy val ansiTagless =
22 | project.in(file("sqlz-tagless/ansi"))
23 | .enablePlugins(SbtOsgi/*, BuildInfoPlugin*/)
24 | .settings(name := "sqlz-tagless-ansi")
25 | .settings(description := "")
26 | .settings(sqlzSettings ++ publishSettings("sqlz"))
27 | .settings(libraryDependencies ++= Seq(specsNoIt))
28 |
29 | lazy val ansiAlgebra =
30 | project.in(file("sqlz-algebra/ansi"))
31 | .enablePlugins(SbtOsgi/*, BuildInfoPlugin*/)
32 | .settings(name := "sqlz-algebra-ansi")
33 | .settings(description := "")
34 | .settings(sqlzSettings ++ publishSettings("sqlz"))
35 | .settings(libraryDependencies ++= Seq(scalaz, specsNoIt))
36 | .dependsOn(ansiTagless)
37 |
38 | lazy val jdbc=
39 | project.in(file("sqlz-jdbc"))
40 | .enablePlugins(SbtOsgi/*, BuildInfoPlugin*/)
41 | .settings(name := "sqlz-jdbc")
42 | .settings(description := "")
43 | .settings(sqlzSettings ++ publishSettings("sqlz"))
44 | .settings(libraryDependencies ++= Seq(scalaz, specsNoIt))
45 | .dependsOn(ansiTagless)
46 |
47 | lazy val data =
48 | project.in(file("sqlz-data"))
49 | .enablePlugins(SbtOsgi)
50 | .settings(name := "sqlz-data")
51 | .settings(description := "Data structures for sqlz.")
52 | .settings(sqlzSettings ++ publishSettings("sqlz"))
53 | .settings(libraryDependencies ++= Seq(specsNoIt))
54 |
--------------------------------------------------------------------------------
/codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | precision: 2
3 | round: down
4 | range: 50...100
5 |
6 | notify:
7 | gitter:
8 | default:
9 | url: "https://webhooks.gitter.im/e/265467704897e83ddb9a"
10 | threshold: "0.1%"
11 |
--------------------------------------------------------------------------------
/project/SqlzUtil.scala:
--------------------------------------------------------------------------------
1 | import sbt._
2 | import Keys._
3 | import com.typesafe.sbt.osgi.SbtOsgi.autoImport._
4 | import com.typesafe.sbt.osgi.SbtOsgi.autoImport.OsgiKeys._
5 | import sbtrelease.ReleasePlugin.autoImport._
6 | import sbtrelease.ReleasePlugin.autoImport.ReleaseTransformations._
7 | import tut.TutPlugin.autoImport._
8 | import com.typesafe.sbt.SbtPgp.autoImport._
9 | import sbtbuildinfo.BuildInfoPlugin.autoImport._
10 |
11 | object SqlzUtil {
12 | lazy val unit: Unit = ()
13 |
14 | lazy val scalaz = "org.scalaz" %% "scalaz-core" % "7.2.10"
15 | lazy val specsBase = "org.specs2" %% "specs2-core" % "3.8.8"
16 | lazy val specs = "org.specs2" %% "specs2-core" % "3.8.8" % "test,it"
17 | lazy val specsNoIt = "org.specs2" %% "specs2-core" % "3.8.8" % "test"
18 | lazy val publishAllSigned = taskKey[Unit]("Publish all (run with +publishAll for crossbuilds)")
19 |
20 | lazy val noPublishSettings = Seq(
21 | publish := unit,
22 | publishLocal := unit,
23 | publishArtifact := false
24 | )
25 |
26 | lazy val buildSettings = Seq(
27 | scalaVersion := "2.12.4",
28 | organization := "com.github.jacoby6000",
29 | licenses += ("MIT", url("http://opensource.org/licenses/MIT")),
30 | crossScalaVersions := Seq("2.11.11", scalaVersion.value),
31 | autoAPIMappings := true
32 | )
33 |
34 | lazy val scalacVersionOptions =
35 | Map(
36 | "2.12" -> Seq(
37 | "-deprecation", // Emit warning and location for usages of deprecated APIs.
38 | "-encoding", "utf-8", // Specify character encoding used by source files.
39 | "-explaintypes", // Explain type errors in more detail.
40 | "-feature", // Emit warning and location for usages of features that should be imported explicitly.
41 | "-language:existentials", // Existential types (besides wildcard types) can be written and inferred
42 | "-language:experimental.macros", // Allow macro definition (besides implementation and application)
43 | "-language:higherKinds", // Allow higher-kinded types
44 | "-language:implicitConversions", // Allow definition of implicit functions called views
45 | "-unchecked", // Enable additional warnings where generated code depends on assumptions.
46 | "-Xcheckinit", // Wrap field accessors to throw an exception on uninitialized access.
47 | "-Xfatal-warnings", // Fail the compilation if there are any warnings.
48 | "-Xfuture", // Turn on future language features.
49 | "-Xlint:adapted-args", // Warn if an argument list is modified to match the receiver.
50 | "-Xlint:by-name-right-associative", // By-name parameter of right associative operator.
51 | "-Xlint:constant", // Evaluation of a constant arithmetic expression results in an error.
52 | "-Xlint:delayedinit-select", // Selecting member of DelayedInit.
53 | "-Xlint:doc-detached", // A Scaladoc comment appears to be detached from its element.
54 | "-Xlint:inaccessible", // Warn about inaccessible types in method signatures.
55 | "-Xlint:infer-any", // Warn when a type argument is inferred to be `Any`.
56 | "-Xlint:missing-interpolator", // A string literal appears to be missing an interpolator id.
57 | "-Xlint:nullary-override", // Warn when non-nullary `def f()' overrides nullary `def f'.
58 | "-Xlint:nullary-unit", // Warn when nullary methods return Unit.
59 | "-Xlint:option-implicit", // Option.apply used implicit view.
60 | "-Xlint:package-object-classes", // Class or object defined in package object.
61 | "-Xlint:poly-implicit-overload", // Parameterized overloaded implicit methods are not visible as view bounds.
62 | "-Xlint:private-shadow", // A private field (or class parameter) shadows a superclass field.
63 | "-Xlint:stars-align", // Pattern sequence wildcard must align with sequence component.
64 | "-Xlint:type-parameter-shadow", // A local type parameter shadows a type already in scope.
65 | "-Xlint:unsound-match", // Pattern match may not be typesafe.
66 | "-Yno-adapted-args", // Do not adapt an argument list (either by inserting () or creating a tuple) to match the receiver.
67 | "-Ypartial-unification", // Enable partial unification in type constructor inference
68 | "-Ywarn-dead-code", // Warn when dead code is identified.
69 | "-Ywarn-extra-implicit", // Warn when more than one implicit parameter section is defined.
70 | "-Ywarn-inaccessible", // Warn about inaccessible types in method signatures.
71 | "-Ywarn-infer-any", // Warn when a type argument is inferred to be `Any`.
72 | "-Ywarn-nullary-override", // Warn when non-nullary `def f()' overrides nullary `def f'.
73 | "-Ywarn-nullary-unit", // Warn when nullary methods return Unit.
74 | "-Ywarn-numeric-widen", // Warn when numerics are widened.
75 | "-Ywarn-unused:imports", // Warn if an import selector is not referenced.
76 | "-Ywarn-unused:locals", // Warn if a local definition is unused.
77 | "-Ywarn-unused:patvars", // Warn if a variable bound in a pattern is unused.
78 | "-Ywarn-unused:privates", // Warn if a private member is unused.
79 | "-Ywarn-value-discard" // Warn when non-Unit expression results are unused.
80 | ),
81 | "2.11" -> Seq(
82 | "-encoding", "UTF-8", // 2 args
83 | "-feature",
84 | "-language:existentials",
85 | "-language:higherKinds",
86 | "-language:implicitConversions",
87 | "-language:experimental.macros",
88 | "-unchecked",
89 | "-Xlint",
90 | "-Yno-adapted-args",
91 | "-Ywarn-dead-code",
92 | "-Ywarn-value-discard",
93 | "-Xmax-classfile-name", "128",
94 | "-Xfatal-warnings"
95 | )
96 | )
97 |
98 |
99 | lazy val commonSettings = Seq(
100 | scalacOptions ++= scalacVersionOptions((scalaVersion in Compile).value.split('.').dropRight(1).mkString(".")),
101 | scalacOptions in (Compile, doc) ++= Seq(
102 | "-groups",
103 | "-sourcepath", (baseDirectory in LocalRootProject).value.getAbsolutePath,
104 | "-doc-source-url", "https://github.com/jacoby6000/sqlz/tree/v" + version.value + "${FILE_PATH}.scala"
105 | ),
106 | addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.9.3")
107 | )
108 |
109 | def publishSettings(packageName: String) = osgiSettings ++ Seq(
110 | //buildInfoKeys := Seq(name, version, scalaVersion, sbtVersion),
111 | //buildInfoPackage := packageName + ".build",
112 | //buildInfoKeys ++= Seq[BuildInfoKey](
113 | //resolvers,
114 | //libraryDependencies in Test,
115 | //BuildInfoKey.map(name) { case (k, v) => "project" + k.capitalize -> v.capitalize },
116 | //BuildInfoKey.action("buildTime") { System.currentTimeMillis }
117 | //),
118 | exportPackage := Seq("sqlz.*"),
119 | privatePackage := Seq(),
120 | dynamicImportPackage := Seq("*"),
121 | publishMavenStyle := true,
122 | publishTo := {
123 | val nexus = "https://oss.sonatype.org/"
124 | if (isSnapshot.value)
125 | Some("snapshots" at nexus + "content/repositories/snapshots")
126 | else
127 | Some("releases" at nexus + "service/local/staging/deploy/maven2")
128 | },
129 | publishArtifact in Test := false,
130 | homepage := Some(url("https://github.com/jacoby6000/sqlz")),
131 | apiURL := Some(url("https://github.com/jacoby6000/sqlz/tree/master")),
132 | pomIncludeRepository := Function.const(false),
133 | pomExtra :=
134 |
135 | git@github.com:Jacoby6000/sqlz.git
136 | scm:git:git@github.com:Jacoby6000/sqlz.git
137 |
138 |
139 |
140 | Jacoby6000
141 | Jacob Barber
142 | http://jacoby6000.github.com/
143 | Jacoby6000@gmail.com
144 |
145 | ,
146 | releaseCrossBuild := true,
147 | releasePublishArtifactsAction := PgpKeys.publishSigned.value,
148 | releaseProcess := Seq[ReleaseStep](
149 | checkSnapshotDependencies,
150 | inquireVersions,
151 | runClean,
152 | //ReleaseStep(action = Command("package", _)),
153 | setReleaseVersion,
154 | commitReleaseVersion,
155 | tagRelease,
156 | //ReleaseStep(action = Command.process("publishSigned", _)),
157 | setNextVersion,
158 | commitNextVersion,
159 | //ReleaseStep(action = Command.process("sonatypeReleaseAll", _)),
160 | pushChanges
161 | )
162 | )
163 |
164 | lazy val sqlzSettings = buildSettings ++ commonSettings
165 | }
166 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.0.3
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | logLevel := Level.Warn
2 |
3 | resolvers += Resolver.typesafeRepo("releases")
4 |
5 | addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.6.2")
6 | addSbtPlugin("com.47deg" % "sbt-microsites" % "0.7.13")
7 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1")
8 | addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.1")
9 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0")
10 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.7")
11 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.0" )
12 | addSbtPlugin("com.typesafe.sbt" % "sbt-osgi" % "0.9.2")
13 | addSbtPlugin("com.typesafe.sbt" % "sbt-ghpages" % "0.6.2")
14 | addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.7.0")
15 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Abandoned
2 |
3 | I am no longer working on this. Master is currently very broken, and probably does not have good architecture. It is a partial implementation of SQL using tagless algebras...
4 |
5 | I am giving up, because the way to correctly do this would be with a mutually recursive AST. I had implemented this in an old version of dotty (see the dotty branch) where it _appeared_ to work, but there were some issues when evaluating the recursive parts of the algebra for the paramorphism. Also, I should've been using `histoM` instead of `paraM` looking back... Anyway, it doesn't matter. Scala doesn't support GADTs and what I had working with Dotty no longer compiles for reasons I don't understand (though, admittedly, I didn't try that hard).
6 |
7 | This should be correctly doable using tagless algebras as well, however I am not really a fan of that approach for this particular problem. Modelling SQL using a mutually recursive AST felt right... Tagless doesn't. That's just my opinion though. Feel free to pick this up if you want.
8 |
9 | ------------------
10 |
11 | [](https://maven-badges.herokuapp.com/maven-central/com.github.jacoby6000/scoobie-core_2.12)
12 | [](https://gitter.im/Jacoby6000/Scala-SQL-AST?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
13 | [](https://travis-ci.org/Jacoby6000/scoobie)
14 | [](https://codecov.io/gh/Jacoby6000/scoobie)
15 |
--------------------------------------------------------------------------------
/scripts/build.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 | sbt ++$TRAVIS_SCALA_VERSION compile coverageReport test
3 |
--------------------------------------------------------------------------------
/scripts/publishSite.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -e
3 |
4 | git config --global user.email "Jacoby6000@gmail.com "
5 | git config --global user.name "Jacoby6000"
6 | git config --global push.default simple
7 |
8 | sbt docs/publishMicrosite
9 |
--------------------------------------------------------------------------------
/sqlz-algebra/ansi/src/main/scala/sqlz/algebra/ansi/Newtypes.scala:
--------------------------------------------------------------------------------
1 | package sqlz.algebra
2 |
3 |
4 | object newts {
5 | trait NewTypeSyntax {
6 | implicit def toReprOps[A](a: A): NewType.ReprOps[A] = new NewType.ReprOps[A](a)
7 | implicit def toNewTypeOps[A](a: A): NewType.NewTypeOps[A] = new NewType.NewTypeOps[A](a)
8 | }
9 |
10 | trait NewType[T, R] {
11 | final type Type = T
12 | final type Repr = R
13 |
14 | def subst[F[_]](fa: F[Repr]): F[T]
15 |
16 | def unsubst[F[_]](fa: F[T]): F[Repr] = {
17 | type f[x] = F[x] => F[Repr]
18 | subst[f](identity[F[Repr]])(fa)
19 | }
20 |
21 | def wrap(r: Repr): T = {
22 | type f[x] = x
23 | subst[f](r)
24 | }
25 |
26 | def unwrap(r: T): Repr = {
27 | type f[x] = x
28 | unsubst[f](r)
29 | }
30 | }
31 | object NewType {
32 | type Aux[T, R] = NewType[T, R]
33 |
34 | trait CompanionBase {
35 | protected type Upper
36 | type Repr <: Upper
37 |
38 | type Base <: Upper
39 | trait Tag extends Any
40 | type Type <: (Base with Tag)
41 | }
42 | trait Companion[U, R <: U] extends CompanionBase {
43 | final type Upper = U
44 | final type Repr = R
45 | }
46 |
47 | trait Default { self: CompanionBase =>
48 | def apply(value: Repr): Type =
49 | newType.wrap(value)
50 |
51 | def unapply(value: Type): Some[Repr] =
52 | Some(newType.unwrap(value))
53 |
54 | implicit val newType: NewType.Aux[Type, Repr] = new NewType[Type, Repr] {
55 | // type Repr = self.Repr
56 | def subst[F[_]](fa: F[Repr]): F[Type] = fa.asInstanceOf[F[Type]]
57 | }
58 | }
59 |
60 | trait Of[R] extends Companion[Any, R] with Default
61 | trait TranslucentOf[R] extends Companion[R, R] with Default
62 | trait OfRef[R <: AnyRef] extends Companion[AnyRef, R] with Default
63 |
64 | trait Refined[R] extends Companion[R, R] {
65 | def isValid(r: R): Boolean
66 |
67 | def apply(value: R): Option[Type] =
68 | if (isValid(value)) Some(value.asInstanceOf[Type])
69 | else None
70 | }
71 |
72 | final class ReprOps[R](val value: R) extends AnyVal {
73 | def nu[T](implicit N: NewType[T, R]): T = N.wrap(value)
74 | }
75 | final class NewTypeOps[T](val value: T) extends AnyVal {
76 | def un[R](implicit N: NewType.Aux[T, R]): R = N.unwrap(value)
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/sqlz-algebra/ansi/src/main/scala/sqlz/algebra/ansi/jdbc.scala:
--------------------------------------------------------------------------------
1 | package sqlz.algebra.ansi
2 |
3 | import sqlz.tagless.ansi._
4 | import java.sql.{Connection, PreparedStatement}
5 | import java.io.InputStream
6 | import scalaz._
7 |
8 | object jdbc {
9 |
10 | //TODO: Newts for safety.
11 |
12 | object algebras {
13 | class QueryValueFragmentAlg[F[_], A](fragment: Fragment[F, A]) extends QueryValue[F, A] {
14 | import fragment.syntax._
15 |
16 | def param[T: F](t: T): A = t.prepare
17 | def function(path: Path, args: List[A]): A =
18 | args match {
19 | case Nil => path.path.raw ++ sql"()"
20 | case h :: t => path.path.raw ++ sql"(" ++ t.foldLeft(h)((acc, arg) => acc ++ sql", " ++ arg) ++ sql")"
21 | }
22 |
23 | def nul: A = sql"NULL"
24 | }
25 |
26 | class QueryComparisonFragmentAlg[F[_], A](fragment: Fragment[F, A]) extends QueryComparison[A, A] {
27 | import fragment.syntax._
28 |
29 | def equal(l: A, r: A): A = l ++ sql" = " ++ r
30 | def greaterThan(l: A, r: A): A = l ++ sql" > " ++ r
31 | def greaterThanOrEqual(l: A, r: A): A = l ++ sql" >= " ++ r
32 | def lessThan(l: A, r: A): A = l ++ sql" < " ++ r
33 | def lessThanOrEqual(l: A, r: A): A = l ++ sql" <= " ++ r
34 | def in(value: A, compareTo: List[A]): A =
35 | compareTo match {
36 | case Nil => sql"1=0" // crude optimization. Goes away when I introduce State
37 | case h :: t => value ++ sql" IN (" ++ t.foldLeft(h)((acc, arg) => acc ++ sql", " ++ arg) ++ sql")"
38 | }
39 |
40 | def lit(u: A): A = u
41 |
42 | def not(a: A): A = sql"NOT " ++ a
43 | def and(l: A, r: A): A = l ++ sql" AND " ++ r
44 | def or(l: A, r: A): A = l ++ sql" OR " ++ r
45 | }
46 | }
47 |
48 | class QueryProjectionFragmentAlg[F[_], A](fragment: Fragment[F, A]) extends QueryProjection[A, A] {
49 | import fragment.syntax._
50 |
51 | def all: A =
52 | sql"*"
53 |
54 | def one(selection: A, alias: Option[Path]): A =
55 | selection ++ alias.map(aliasPath => sql" AS " ++ aliasPath.path.raw).getOrElse(sql"")
56 | }
57 |
58 | class QueryJoinFragmentAlg[F[_], A](fragment: Fragment[F, A]) extends QueryJoin[A, A, A] {
59 | import fragment.syntax._
60 |
61 | def buildJoin(joinString: A, table: A, condition: A): A = joinString ++ table ++ sql" ON " ++ condition
62 |
63 | def inner(table: A, condition: A): A = buildJoin(sql" INNER JOIN ", table, condition)
64 | def fullOuter(table: A, condition: A): A = buildJoin(sql" FULL OUTER JOIN ", table, condition)
65 | def leftOuter(table: A, condition: A): A = buildJoin(sql" LEFT OUTER JOIN ", table, condition)
66 | def rightOuter(table: A, condition: A): A = buildJoin(sql" RIGHT OUTER JOIN ", table, condition)
67 | def cross(table: A, condition: A): A = buildJoin(sql" INNER JOIN ", table, condition)
68 | }
69 |
70 | class QuerySortFragmentAlg[F[_], A](fragment: Fragment[F, A]) extends QuerySort[A] {
71 | import fragment.syntax._
72 |
73 | def ascending(queryPath: Path): A = queryPath.path.raw + sql" ASC"
74 | def descending(queryPath: Path): A = queryPath.path.raw + sql" DESC"
75 | }
76 |
77 | class QueryExpressionFragmentAlg[F[_], A] extends QueryExpression[A, A, A, A, A, A, A] {
78 | import fragment.syntax._
79 |
80 | def select(table: A, values: NonEmptyList[A], joins: List[A], filter: Option[A], sorts: List[A], groupings: List[A], offset: Option[Long], limit: Option[Long]): A = {
81 |
82 | val valuesSql = values.tail.foldLeft(values.head)((acc, elem) => acc ++ sql", " ++ elem)
83 |
84 | val baseQuery = sql"SELECT " ++ valuesSql ++ sql" FROM " ++ table
85 |
86 | baseQuery ++
87 | filter.map(where => baseQuery ++ sql" WHERE " ++ where).getOrElse(sql"")
88 | }
89 |
90 | def insert(collection: Path, values: List[(Path, A)]): A =
91 | def update(collection: Path, values: List[(Path, A)], where: A): A
92 | def delete(collection: Path, where: A): A
93 | }
94 |
95 | trait Fragment[F[_], A] { self =>
96 | def prepare[T: F](t: T): A
97 | def query(str: String): A
98 | def concat(l: A, r: A): A
99 |
100 | object syntax {
101 |
102 | val semigroup = semigroupFromFragment(self)
103 |
104 | implicit class FragmentOps(a: A) {
105 | def ++(other: A): A = concat(a, other)
106 | }
107 |
108 | implicit class StringOps(s: String) {
109 | def raw: A = query(s)
110 | }
111 |
112 | implicit class TOps[T](t: T) {
113 | def prepare(implicit F: F[T]): A = self.prepare(t)
114 | }
115 |
116 | // maybe get rid of this and just implicitly convert to A given T and implicit F[T].
117 | // Not sure yet... Would avoid extra boxing, but might be dangerous.
118 | // Bsaically I'm stealing the strategy used for the show interpolator in scalaz.
119 | case class Prepped[T] private (t: T, prepper: F[T]) { def prep: A = prepare(t)(prepper) }
120 | implicit def toPrepped[T: F](t: T): Prepped[T] = Prepped(t, implicitly)
121 |
122 | implicit class StringContextExtensions(ctx: StringContext) {
123 | // This is terrible, but I'm feeling lazy.
124 | def interpolator(args: Seq[A]): A =
125 | (args zip ctx.parts).foldLeft(query("")) {
126 | case (acc, (param, quer)) =>
127 | concat(acc, concat(param, query(quer)))
128 | }
129 |
130 | def sql(args: Prepped[_]*): A = interpolator(args.map(_.prep))
131 | }
132 | }
133 | }
134 |
135 | def semigroupFromFragment[F[_], A](fragment: Fragment[F, A]): Semigroup[A] =
136 | Semigroup.instance(fragment.concat(_, _))
137 |
138 | object algebra {
139 | // TODO: Get rid of this intermediate structure. It should not be necessary; I just can't work out how to get rid of it yet.
140 | sealed trait PreparableSql {
141 | /**
142 | * Produces the PreparedStatement for this query, without the prepared parameters applied.
143 | */
144 | val unpreparedStatement: Connection => PreparedStatement = conn =>
145 | conn.prepareStatement(sqlString)
146 |
147 | val preparedStatement: Connection => PreparedStatement = conn => gatherPreparations.foldLeft(unpreparedStatement(conn))((stmt, f) => f(stmt))
148 |
149 | lazy val gatherPreparations: List[PreparedStatement => PreparedStatement] = {
150 | def gatherWithIndex(preparableSql: PreparableSql): List[(PreparedStatement, Int) => PreparedStatement] =
151 | this match {
152 | case Prepare(f) => List(f)
153 | case Combine(l, r) => gatherWithIndex(l) ++ gatherWithIndex(r)
154 | case QueryString(_) => List()
155 | }
156 |
157 | gatherWithIndex(this).zipWithIndex.map { case (f, idx) => f((_: PreparedStatement), idx) }
158 | }
159 |
160 | lazy val sqlString: String =
161 | this match {
162 | case Prepare(_) => "?"
163 | case Combine(l, r) => l.sqlString + r.sqlString
164 | case QueryString(s) => s
165 | }
166 | }
167 |
168 | case class Prepare(f: (PreparedStatement, Int) => PreparedStatement) extends PreparableSql
169 | case class Combine(l: PreparableSql, r: PreparableSql) extends PreparableSql
170 | case class QueryString(s: String) extends PreparableSql
171 |
172 | trait Preparable[A] {
173 | def prepare(a: A): (PreparedStatement, Int) => PreparedStatement
174 | def contramap[B](f: B => A): Preparable[B] = Preparable.instance[B]((stmt, idx, b) => prepare(f(b))(stmt, idx))
175 | }
176 |
177 | object Preparable {
178 | def apply[A: Preparable]: Preparable[A] = implicitly[Preparable[A]]
179 |
180 | @inline def instance[A](f: (PreparedStatement, Int, A) => PreparedStatement): Preparable[A] =
181 | new Preparable[A] {
182 | def prepare(a: A): (PreparedStatement, Int) => PreparedStatement = f(_, _, a)
183 | }
184 |
185 | @inline def instanceU[A](f: (PreparedStatement, Int, A) => Unit): Preparable[A] =
186 | new Preparable[A] {
187 | def prepare(a: A): (PreparedStatement, Int) => PreparedStatement = (stmt, n) => {f(stmt, n, a); stmt}
188 | }
189 |
190 | def from[A, B: Preparable](f: A => B): Preparable[A] = Preparable[B].contramap(f)
191 |
192 | implicit val preparableBoolean: Preparable[Boolean] = instanceU(_.setBoolean(_, _))
193 | implicit val preparableByte: Preparable[Byte] = instanceU(_.setByte(_, _))
194 | implicit val preparableShort: Preparable[Short] = instanceU(_.setShort(_, _))
195 | implicit val preparableInt: Preparable[Int] = instanceU(_.setInt(_, _))
196 | implicit val preparableLong: Preparable[Long] = instanceU(_.setLong(_, _))
197 | implicit val preparableFloat: Preparable[Float] = instanceU(_.setFloat(_, _))
198 | implicit val preparableDouble: Preparable[Double] = instanceU(_.setDouble(_, _))
199 | implicit val preparableString: Preparable[String] = instanceU(_.setString(_, _))
200 |
201 | implicit val preparableRef: Preparable[java.sql.Ref] = instanceU(_.setRef(_, _))
202 | implicit val preparableTime: Preparable[java.sql.Time] = instanceU(_.setTime(_, _))
203 | implicit val preparableDate: Preparable[java.sql.Date] = instanceU(_.setDate(_, _))
204 | implicit val preparableTimestamp: Preparable[java.sql.Timestamp] = instanceU(_.setTimestamp(_, _))
205 |
206 | implicit val preparableInputStream: Preparable[InputStream] = instanceU(_.setBlob(_, _))
207 | }
208 |
209 | object SqlFragmentAlg extends Fragment[Preparable, PreparableSql] {
210 | def prepare[T: Preparable](t: T): PreparableSql = Prepare(Preparable[T].prepare(t))
211 | def query(str: String): PreparableSql = QueryString(str)
212 | def concat(l: PreparableSql, r: PreparableSql): PreparableSql = Combine(l, r)
213 | }
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/sqlz-tagless/ansi/src/main/scala/sqlz/tagless/PreparableSql.scala:
--------------------------------------------------------------------------------
1 | package sqlz.algebra
2 |
3 | import java.sql.{PreparedStatement, Connection}
4 |
5 | sealed trait PreparableSql {
6 | def unpreparedStatement: Connection => PreparedStatement = conn =>
7 | conn.prepareStatement(sqlString)
8 |
9 | def preparedStatement: Connection => PreparedStatement = conn => gatherPreparations.foldLeft(unpreparedStatement(conn))((stmt, f) => f(stmt))
10 |
11 | def gatherPreparations: List[PreparedStatement => PreparedStatement] = {
12 | def gatherWithIndex(preparableSql: PreparableSql): List[(PreparedStatement, Int) => PreparedStatement] =
13 | this match {
14 | case Prepare(f) => List(f)
15 | case Combine(l, r) => gatherWithIndex(l) ++ gatherWithIndex(r)
16 | case QueryString(_) => List()
17 | }
18 |
19 | gatherWithIndex(this).zipWithIndex.map { case (f, idx) => f((_: PreparedStatement), idx) }
20 | }
21 |
22 | def sqlString: String =
23 | this match {
24 | case Prepare(_) => "?"
25 | case Combine(l, r) => l.sqlString + r.sqlString
26 | case QueryString(s) => s
27 | }
28 | }
29 |
30 | case class Prepare(f: (PreparedStatement, Int) => PreparedStatement) extends PreparableSql
31 | case class Combine(l: PreparableSql, r: PreparableSql) extends PreparableSql
32 | case class QueryString(s: String) extends PreparableSql
33 |
34 |
--------------------------------------------------------------------------------
/sqlz-tagless/ansi/src/main/scala/sqlz/tagless/ansi.scala:
--------------------------------------------------------------------------------
1 | package sqlz.tagless
2 |
3 | object ansi {
4 | case class Path(path: String) extends AnyVal
5 |
6 | trait QueryValue[F[_], A] {
7 | def param[U: F](u: U): A
8 | def function(path: Path, args: List[A]): A
9 | def nul: A
10 | }
11 |
12 | trait QueryComparison[U, A] {
13 | // Compositions with values
14 | def equal(l: U, r: U): A
15 | def greaterThan(l: U, r: U): A
16 | def greaterThanOrEqual(l: U, r: U): A
17 | def lessThan(l: U, r: U): A
18 | def lessThanOrEqual(l: U, r: U): A
19 | def in(value: U, compareTo: List[U]): A
20 | def lit(u: U): A
21 |
22 | // Compositions with other comparisons
23 | def not(a: A): A
24 | def and(l: A, r: A): A
25 | def or(l: A, r: A): A
26 | }
27 |
28 | trait QueryProjection[U, A] {
29 | def all: A
30 | def one(selection: U, alias: Option[Path]): A
31 | }
32 |
33 | trait QueryJoin[T, U, A] {
34 | def inner(table: U, condition: T): A
35 | def fullOuter(table: U, condition: T): A
36 | def leftOuter(table: U, condition: T): A
37 | def rightOuter(table: U, condition: T): A
38 | def cross(table: U, condition: T): A
39 | }
40 |
41 | trait QuerySort[A] {
42 | def ascending(queryPath: Path): A
43 | def descending(queryPath: Path): A
44 | }
45 |
46 | trait QueryExpression[T, U, V, W, X, A, B] {
47 | def select(table: U, values: List[U], joins: List[T], filter: Option[V], sorts: List[W], groupings: List[W], offset: Option[Long], limit: Option[Long]): A
48 |
49 | def insert(collection: Path, values: List[(Path, X)]): B
50 | def update(collection: Path, values: List[(Path, X)], where: V): B
51 | def delete(collection: Path, where: V): B
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/version.sbt:
--------------------------------------------------------------------------------
1 | version in ThisBuild := "0.3.1"
2 |
--------------------------------------------------------------------------------