├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.sbt ├── project ├── Bolerplate.scala ├── PublishSettings.scala ├── build.properties └── plugins.sbt └── src ├── main └── scala │ └── typed │ └── sql │ ├── ColumnQ.scala │ ├── Shape.scala │ ├── Table.scala │ ├── ast │ └── quieries.scala │ ├── conds.scala │ ├── internal │ ├── FSHOps.scala │ ├── FieldNames.scala │ ├── FlattenOption.scala │ ├── InsertValuesInfer.scala │ ├── MkWrite.scala │ ├── OrderByInfer.scala │ ├── RemoveFields.scala │ ├── Repr2Ops.scala │ ├── SelectInfer.scala │ ├── TupleAppend.scala │ └── WhereInfer.scala │ ├── prefixes │ ├── InsertIntoPrefix.scala │ ├── SelectionPrefix.scala │ ├── UpdationPrefix.scala │ └── joinPrefixes.scala │ ├── queries.scala │ ├── syntax.scala │ └── toDoobie.scala └── test └── scala └── typed └── sql ├── H2Test.scala ├── PGTest.scala └── TestSyntax.scala /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | build 3 | dist 4 | env-* 5 | *.egg-info 6 | *.pyc 7 | .idea 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | 3 | before_install: 4 | - export TRAVIS_BRANCH=`echo $TRAVIS_BRANCH | sed 's%[/_]%-%g'` 5 | 6 | script: 7 | - sbt test 8 | 9 | cache: 10 | directories: 11 | - $HOME/.ivy2 12 | - $HOME/.sbt 13 | - $HOME/.coursier 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | 204 | ======================================================================= 205 | Mist Subcomponents: 206 | 207 | The Mist project contains subcomponents with separate copyright 208 | notices and license terms. Your use of the source code for the these 209 | subcomponents is subject to the terms and conditions of the following 210 | licenses. 211 | 212 | ======================================================================== 213 | Apache licenses Version 2.0, January 2004 http://www.apache.org/licenses/ 214 | ======================================================================== 215 | 216 | The following components are provided under a Apache-style license. See project link for details. 217 | (Apache 2.0 License) Copyright (C) 2016 The Apache Software Foundation. spark-core, spark-sql, spark-hive (https://github.com/apache/spark) 218 | (Apache 2.0 License) json4s-native 3.2.10, json4s-jackson 3.2.10 (https://github.com/json4s/json4s) 219 | (Apache 2.0 License) Copyright (C) 2011-2016 Lightbend Inc. config 1.3.0, akka-http-core-experimental 2.0.1, akka-http-experimental 2.0.1, akka-http-spray-json-experimental 2.0.1, scalatic 2.2.6, scalatest 2.2.6, akka-testkit 2.3.12 (http://akka.io/) 220 | (ASL)&(LGPL) Copyright (C) 2016 Newtonsoft. json-schema-validator 2.2.6 (https://github.com/fge/json-schema-validator) 221 | (Apache 2.0 License) Copyright (C) 2016 MapDB. mapdb 3.0.1 (https://github.com/fcabestre/Scala-MQTT-client) 222 | 223 | 224 | ======================================================================== 225 | BSD-style licenses 226 | ======================================================================== 227 | 228 | The following components are provided under a BSD-style license. See project link for details. 229 | 230 | (BSD-like) Copyright (C) 2002-2016 École Polytechnique Fédérale de Lausanne (EPFL). (org.scala-lang:scala-actors: 2.10.6, 2.11.8 - http://www.scala-lang.org/) 231 | (BSD-like) Copyright (C) 2002-2016 École Polytechnique Fédérale de Lausanne (EPFL). (org.scala-lang:scala-compiler:2.10.6, 2.11.8 - http://www.scala-lang.org/) 232 | (BSD-like) Copyright (C) 2002-2016 École Polytechnique Fédérale de Lausanne (EPFL). (org.scala-lang:scala-reflect:2.10.6, 2.11.8 - http://www.scala-lang.org/) 233 | (BSD-like) Copyright (C) 2002-2016 École Polytechnique Fédérale de Lausanne (EPFL). (org.scala-lang:scala-library:2.10.6, 2.11.8 - http://www.scala-lang.org/) 234 | (The New BSD License) Copyright (C) 2009-2015, Barthélémy Dagenais. Py4J (net.sf.py4j:py4j: 0.8.2.1, 0.9.2, 0.10.1- http://py4j.sourceforge.net/) 235 | (BSD licence) SBT (http://www.scala-sbt.org) 236 | 237 | ======================================================================== 238 | MIT licenses 239 | ======================================================================== 240 | 241 | The following components are provided under a MIT license. See project link for details. 242 | 243 | (MIT licenses) Copyright (c) 2004-2013 QOS.ch. slf4j-api 1.0.3, 1.7.21 244 | 245 | ======================================================================== 246 | EPL-1.0 licenses 247 | ======================================================================== 248 | 249 | The following components are provided under a EPL-1.0 license. See project link for details. 250 | 251 | (EPL-1.0) Copyright (c) 2004-2013 QOS.ch. logback-classic 1.1.7 Copyright (C) 1999-2012, QOS.ch. 252 | 253 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Typed-sql 2 | 3 | This is a frontend library for writing [doobie](https://github.com/tpolecat/doobie) queries. 4 | Its goal to provide a typesafe-dsl for it and keep its constructs as close as it possible to the plain SQL language. 5 | 6 | ### Usage 7 | In addition to doobie dependencies add the following one to your build.sbt: 8 | ```scala 9 | libraryDependencies += Seq( 10 | "io.hydrosphere" %% "typed-sql" % "0.1.0" 11 | ) 12 | ``` 13 | 14 | Import: 15 | ```scala 16 | import doobie._ 17 | import doobie.implicits._ 18 | import doobie.syntax._ 19 | 20 | import typed.sql.syntax._ 21 | import typed.sql.toDoobie._ 22 | ``` 23 | 24 | Declare case class for row, create table from it and columns: 25 | ```scala 26 | case class Row( 27 | a: Int, 28 | b: String, 29 | c: String 30 | ) 31 | 32 | val table = Table.of[Row].name('test) 33 | // or if `a` column is a primary key and has serial type 34 | val table = Table.of[Row].autoColumn('a).name('test) 35 | 36 | val a = table.col('a) 37 | val b = table.col('b) 38 | val c = table.col('c) 39 | ``` 40 | 41 | Now it's time for to write queries. 42 | Examples: 43 | ```scala 44 | insert.into(table).values(1, "b", "c") 45 | // or if `a` column is a primary key and has serial type 46 | insert.into(table).values("b", "c") 47 | 48 | select(*).from(table) 49 | select(*).from(table).where(a === 1) 50 | select(a, b).from(table) 51 | 52 | update(table).set(b := "Upd B").where(a === 1) 53 | 54 | delete.from(table).where(a === 1) 55 | ``` 56 | 57 | Convert to `Query0`/`Update0` using `toQuery`/`toUpdate`: 58 | ```scala 59 | 60 | val q0: Query0[Row] = select(*).from(table).toQuery 61 | // the same for update and insert 62 | val u0: Update0 = delete.from(table).where(a === 1).toUpdate 63 | ``` 64 | 65 | #### More examples: 66 | Where: 67 | ```scala 68 | select(*).from(table).where(a > 1 and a < 5) 69 | select(*).from(table).where(a >= 1 and a =< 5) 70 | select(*).from(table).where(a === 1 or a === 2) 71 | select(*).from(table).where(b like "BBB%") 72 | select(*).from(table).where(a.in(NonEmptyList.of(1,2,3))) 73 | ``` 74 | 75 | Order By: 76 | ```scala 77 | // SELECT * FROM TEST ORDER BY test.a ASC 78 | select(*).from(table).orderBy(a) 79 | 80 | // SELECT * FROM TEST ORDER BY test.a DESC 81 | select(*).from(table).orderBy(a.DESC) 82 | 83 | // SELECT * FROM TEST ORDER BY test.a ASC test.b ASC 84 | select(*).from(table).orderBy(a, b) 85 | ``` 86 | 87 | Limit/Offset: 88 | ```scala 89 | select(*).from(table).limit(10).offset(1) 90 | ``` 91 | 92 | Joins: 93 | ```scala 94 | case class Row2(a: Int) 95 | val table2 = Table.of[Row2].name('test2) 96 | val a2 = table2.col('a2) 97 | 98 | // Query0[(Row1, Row2)] 99 | select(*).from(table.innerJoin(table2).on(a1 <==> a2)) 100 | // Query0[(Row1, Option[Row2])] 101 | select(*).from(table.leftJoin(table2).on(a1 <==> a2)) 102 | // Query0[(Option[Row1], Row2)] 103 | select(*).from(table.rightJoin(table2).on(a1 <==> a2)) 104 | // Query0[(Option[Row1], Option[Row2])] 105 | select(*).from(table.fullJoin(table2).on(a1 <==> a2)) 106 | 107 | // Query0[(Int, Int)] 108 | select(a, a2).from(table.innerJoin(table2).on(a1 <==> a2)) 109 | ``` 110 | Note: it's possible to join more that two tables 111 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | val root = project.in(file(".")) 2 | .settings(PublishSettings.settings: _*) 3 | .settings( 4 | scalaVersion := "2.12.7", 5 | crossScalaVersions := Seq("2.11.12", scalaVersion.value), 6 | organization := "io.hydrosphere", 7 | name := "typed-sql", 8 | version := "0.1.0", 9 | sourceGenerators in Compile += (sourceManaged in Compile).map(dir => Boilerplate.gen(dir)).taskValue, 10 | libraryDependencies ++= Seq( 11 | "com.chuusai" %% "shapeless" % "2.3.3", 12 | "org.tpolecat" %% "doobie-core" % "0.6.0", 13 | 14 | "org.tpolecat" %% "doobie-h2" % "0.6.0" % "test", 15 | "org.tpolecat" %% "doobie-hikari" % "0.6.0" % "test", 16 | "org.tpolecat" %% "doobie-postgres" % "0.6.0" % "test", 17 | "org.scalatest"%% "scalatest" % "3.0.3" % "test", 18 | "org.tpolecat" %% "doobie-scalatest" % "0.6.0" % "test" 19 | ) 20 | ) 21 | 22 | -------------------------------------------------------------------------------- /project/Bolerplate.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | 3 | /** 4 | * Copied, with some modifications, from https://github.com/milessabin/shapeless/blob/master/project/Boilerplate.scala 5 | * 6 | * Generate a range of boilerplate classes, those offering alternatives with 0-22 params 7 | * and would be tedious to craft by hand 8 | */ 9 | object Boilerplate { 10 | 11 | import scala.StringContext._ 12 | 13 | implicit class BlockHelper(val sc: StringContext) extends AnyVal { 14 | def block(args: Any*): String = { 15 | val interpolated = sc.standardInterpolator(treatEscapes, args) 16 | val rawLines = interpolated split '\n' 17 | val trimmedLines = rawLines map { 18 | _ dropWhile (_.isWhitespace) 19 | } 20 | trimmedLines mkString "\n" 21 | } 22 | } 23 | 24 | 25 | val templates: Seq[Template] = List(GenTupleAppend) 26 | 27 | object GenTupleAppend extends Template { 28 | val filename = "TupleAppendInstances.scala" 29 | 30 | override def range: Range.Inclusive = 1 to 21 31 | override def content(tv: TemplateVals): String = { 32 | import tv._ 33 | block""" 34 | |package typed.sql.internal 35 | | 36 | |trait TupleAppendInstances extends LowPrioTupleAppend { 37 | | 38 | - implicit def tApp${arity}[${`A..N`}, ${`N+1`}]: Aux[${`(A..N)`}, ${`N+1`}, ${`(A..N+1)`}] = null 39 | |} 40 | """ 41 | } 42 | } 43 | 44 | /** Returns a seq of the generated files. As a side-effect, it actually generates them... */ 45 | def gen(dir: File) = for (t <- templates) yield { 46 | val tgtFile = dir / t.packageName / t.filename 47 | IO.write(tgtFile, t.body) 48 | tgtFile 49 | } 50 | 51 | trait Template { self => 52 | 53 | def packageName: String = "typed/sql/internal" 54 | 55 | def createVals(arity: Int): TemplateVals = new TemplateVals(arity) 56 | 57 | def filename: String 58 | def content(tv: TemplateVals): String 59 | def range = 1 to 22 60 | def body: String = { 61 | val rawContents = range map { n => content(createVals(n)) split '\n' filterNot (_.isEmpty) } 62 | val preBody = rawContents.head takeWhile (_ startsWith "|") map (_.tail) 63 | val instances = rawContents flatMap {_ filter (_ startsWith "-") map (_.tail) } 64 | val postBody = rawContents.head dropWhile (_ startsWith "|") dropWhile (_ startsWith "-") map (_.tail) 65 | (preBody ++ instances ++ postBody) mkString "\n" 66 | } 67 | } 68 | 69 | class TemplateVals(val arity: Int) { 70 | val synTypes = (0 until arity) map (n => (n+'A').toChar) 71 | val synVals = (0 until arity) map (n => (n+'a').toChar) 72 | val synTypedVals = (synVals zip synTypes) map { case (v,t) => v + ":" + t} 73 | 74 | val `A..N` = synTypes.mkString(", ") 75 | val `N+1` = (arity + 'A').toChar 76 | val `A..N,Res` = (synTypes :+ "Res") mkString ", " 77 | val `a..n` = synVals.mkString(", ") 78 | val `A::N` = (synTypes :+ "HNil") mkString "::" 79 | val `a::n` = (synVals :+ "HNil") mkString "::" 80 | val `_.._` = Seq.fill(arity)("_").mkString(", ") 81 | val `(A..N)` = if (arity == 1) "Tuple1[A]" else synTypes.mkString("(", ", ", ")") 82 | val `(A..N+1)` = if (arity == 1) "Tuple1[A]" else (synTypes :+ `N+1`).mkString("(", ", ", ")") 83 | val `(_.._)` = if (arity == 1) "Tuple1[_]" else Seq.fill(arity)("_").mkString("(", ", ", ")") 84 | val `(a..n)` = if (arity == 1) "Tuple1(a)" else synVals.mkString("(", ", ", ")") 85 | val `(a._1::a._n)` = ((1 to arity).map(i => s"a._$i") :+ "HNil").mkString("::") 86 | val `a:A..n:N` = synTypedVals mkString ", " 87 | } 88 | } -------------------------------------------------------------------------------- /project/PublishSettings.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import sbt.Keys._ 3 | 4 | object PublishSettings { 5 | 6 | val settings = Seq( 7 | publishMavenStyle := true, 8 | publishTo := { 9 | val nexus = "https://oss.sonatype.org/" 10 | if (isSnapshot.value) 11 | Some("snapshots" at nexus + "content/repositories/snapshots/") 12 | else 13 | Some("releases" at nexus + "service/local/staging/deploy/maven2/") 14 | }, 15 | publishArtifact in Test := false, 16 | pomIncludeRepository := { _ => false }, 17 | 18 | licenses := Seq("Apache 2.0 License" -> url("https://github.com/Hydrospheredata/typed-sql")), 19 | homepage := Some(url("https://github.com/Hydrospheredata/typed-sql")), 20 | scmInfo := Some( 21 | ScmInfo( 22 | url("https://github.com/Hydrospheredata/typed-sql"), 23 | "scm:git@github.com:Hydrospheredata/typed-sql.git" 24 | ) 25 | ), 26 | developers := List( 27 | Developer( 28 | id = "dos65", 29 | name = "Vadim Chelyshov", 30 | url = url("https://github.com/dos65"), 31 | email = "qtankle@gmail.com" 32 | ) 33 | ) 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.2.6 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0") 2 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.0") 3 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/ColumnQ.scala: -------------------------------------------------------------------------------- 1 | package typed.sql 2 | 3 | import WhereCond._ 4 | import cats.data.NonEmptyList 5 | 6 | sealed trait ColumnQuery 7 | 8 | /** 9 | * select * 10 | * 11 | */ 12 | sealed trait All extends ColumnQuery 13 | case object All extends All 14 | 15 | 16 | sealed trait CLN[T] extends ColumnQuery 17 | case class Column[K, V, T] private[sql](k: K, t: T) extends CLN[T] { self => 18 | 19 | def `===`(v: V): Eq[K, V, T] = Eq(v) 20 | def `>`(v: V): Gt[K, V, T] = Gt(v) 21 | def `>=`(v: V): GtOrEq[K, V, T] = GtOrEq(v) 22 | def `<`(v: V): Less[K, V, T] = Less(v) 23 | def `=<`(v: V): LessOrEq[K, V, T] = LessOrEq(v) 24 | def like(v: V)(implicit ev: V =:= String): Like[K, T] = Like(v) 25 | def in(values: NonEmptyList[V]): In[K, V, T] = In(values.toList) 26 | 27 | def `<==>`[K2, T2](c2: Column[K2, V, T2]): JoinCond.Eq[K, V, T, K2, T2] = JoinCond.Eq(self, c2) 28 | 29 | def `:=`(v: V): Assign[K, V, T] = Assign(v) 30 | 31 | def ASC: ASC[K, V, T] = new ASC[K, V, T] 32 | def DESC: DESC[K, V, T] = new DESC[K, V, T] 33 | } 34 | 35 | case class Assign[K, V, T](v: V) 36 | 37 | sealed trait SortOrder 38 | final class ASC[K, V, T] extends SortOrder 39 | final class DESC[K, V, T] extends SortOrder 40 | 41 | trait ColumnSyntax { 42 | 43 | implicit class CmpOpChain[A <: WhereCond](a: A) { 44 | def and[B <: WhereCond](b: B): And[A, B] = And(a, b) 45 | def or[B <: WhereCond](b: B): Or[A, B] = Or(a, b) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/Shape.scala: -------------------------------------------------------------------------------- 1 | package typed.sql 2 | 3 | import shapeless.{HList, LabelledGeneric} 4 | 5 | sealed trait TR 6 | final class TRepr[S, N, Rs <: HList, Ru <: HList] extends TR 7 | 8 | //sealed trait TR2 9 | //final class TRepr2[S, N, Rs <: HList, Ru <: HList] extends TR2 10 | 11 | 12 | /** 13 | * FROM Table shape. 14 | * Describes plain tables|joins 15 | */ 16 | sealed trait FSH 17 | final case class From[A <: TR](repr: A) extends FSH 18 | sealed trait FSHJ[H <: TR, Cond <: JoinCond, T <: FSH] extends FSH { 19 | def repr: H 20 | def cond: Cond 21 | def tail: T 22 | } 23 | 24 | final case class IJ[H <: TR, Cond <: JoinCond, T <: FSH](repr: H, cond: Cond, tail: T) extends FSHJ[H, Cond, T] 25 | final case class LJ[H <: TR, Cond <: JoinCond, T <: FSH](repr: H, cond: Cond, tail: T) extends FSHJ[H, Cond, T] 26 | final case class RJ[H <: TR, Cond <: JoinCond, T <: FSH](repr: H, cond: Cond, tail: T) extends FSHJ[H, Cond, T] 27 | final case class FJ[H <: TR, Cond <: JoinCond, T <: FSH](repr: H, cond: Cond, tail: T) extends FSHJ[H, Cond, T] 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/Table.scala: -------------------------------------------------------------------------------- 1 | package typed.sql 2 | 3 | import shapeless._ 4 | import shapeless.ops.record.Selector 5 | import typed.sql.internal.RemoveFields 6 | import typed.sql.prefixes._ 7 | 8 | case class Table[A, N, Rs <: HList, Ru <: HList]( 9 | repr: TRepr[A, N, Rs, Ru], 10 | nameTyped: N, 11 | name: String 12 | ){ self => 13 | 14 | final def column[V](k: Witness)(implicit sel: Selector.Aux[Rs, k.T, V]): Column[k.T, V, N] = 15 | new Column[k.T, V, N](k.value, nameTyped) 16 | 17 | final def col[V](k: Witness)(implicit sel: Selector.Aux[Rs, k.T, V]): Column[k.T, V, N] = 18 | new Column[k.T, V, N](k.value, nameTyped) 19 | 20 | def innerJoin[S2, N2, Rs2 <: HList, Ru2 <: HList](t2: Table[S2, N2, Rs2, Ru2]): IJPrefix[From[TRepr[A, N ,Rs, Ru]], S2, N2, Rs2, Ru2] = 21 | new IJPrefix(From(self.repr), From(t2.repr)) 22 | 23 | def leftJoin[S2, N2, Rs2 <: HList, Ru2 <: HList](t2: Table[S2, N2, Rs2, Ru2]): LJPrefix[From[TRepr[A, N ,Rs, Ru]], S2, N2, Rs2, Ru2] = 24 | new LJPrefix(From(self.repr), From(t2.repr)) 25 | 26 | def rightJoin[S2, N2, Rs2 <: HList, Ru2 <: HList](t2: Table[S2, N2, Rs2, Ru2]): RJPrefix[From[TRepr[A, N ,Rs, Ru]], S2, N2, Rs2, Ru2] = 27 | new RJPrefix(From(self.repr), From(t2.repr)) 28 | 29 | def fullJoin[S2, N2, Rs2 <: HList, Ru2 <: HList](t2: Table[S2, N2, Rs2, Ru2]): FJPrefix[From[TRepr[A, N ,Rs, Ru]], S2, N2, Rs2, Ru2] = 30 | new FJPrefix(From(self.repr), From(t2.repr)) 31 | } 32 | 33 | object Table { 34 | 35 | object of { 36 | def apply[A] = new TableBuild[A] 37 | 38 | class TableBuild[A] { 39 | 40 | def autoColumn[Rs <: HList, Ru <: HList](k: Witness)( 41 | implicit 42 | labGen: LabelledGeneric.Aux[A, Rs], 43 | rf: RemoveFields.Aux[Rs, k.T :: HNil, Ru] 44 | ): TableBuild2[A, Rs, Ru] = new TableBuild2 45 | 46 | def name[H <: HList](k: Witness)( 47 | implicit 48 | labGen: LabelledGeneric.Aux[A, H], 49 | ev: k.T <:< Symbol 50 | ): Table[A, k.T, H, H] = { 51 | val repr = new TRepr[A, k.T, H, H]() 52 | Table(repr, k.value, k.value.name) 53 | } 54 | 55 | } 56 | 57 | class TableBuild2[A, R <: HList, Ru <: HList] { 58 | 59 | def name(k: Witness)( 60 | implicit 61 | ev: k.T <:< Symbol 62 | ): Table[A, k.T, R, Ru] = { 63 | val repr = new TRepr[A, k.T, R, Ru]() 64 | Table(repr, k.value, k.value.name) 65 | } 66 | } 67 | } 68 | 69 | } 70 | 71 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/ast/quieries.scala: -------------------------------------------------------------------------------- 1 | package typed.sql.ast 2 | 3 | 4 | case class Col(table: String, name: String) 5 | 6 | sealed trait JoinCond extends Product with Serializable 7 | final case class JoinCondEq(col1: Col, col2: Col) extends JoinCond 8 | final case class JoinCondAnd(c1: JoinCond, c2: JoinCond) extends JoinCond 9 | final case class JoinCondOr(c1: JoinCond, c2: JoinCond) extends JoinCond 10 | 11 | 12 | sealed trait Join extends Product with Serializable { 13 | def table: String 14 | def cond: JoinCond 15 | } 16 | final case class InnerJoin(table: String, cond: JoinCond) extends Join 17 | final case class LeftJoin(table: String, cond: JoinCond) extends Join 18 | final case class RightJoin(table: String, cond: JoinCond) extends Join 19 | final case class FullJoin(table: String, cond: JoinCond) extends Join 20 | 21 | sealed trait WhereCond extends Product with Serializable 22 | final case class WhereEq(col: Col) extends WhereCond 23 | final case class Less(col: Col) extends WhereCond 24 | final case class LessOrEq(col: Col) extends WhereCond 25 | final case class Gt(col: Col) extends WhereCond 26 | final case class GtOrEq(col: Col) extends WhereCond 27 | final case class Like(col: Col) extends WhereCond 28 | final case class In(col: Col, size: Int) extends WhereCond 29 | final case class And(c1: WhereCond, c2: WhereCond) extends WhereCond 30 | final case class Or(c1: WhereCond, c2: WhereCond) extends WhereCond 31 | 32 | sealed trait SortOrder 33 | case object ASC extends SortOrder 34 | case object DESC extends SortOrder 35 | case class OrderBy(values: List[(Col, SortOrder)]) 36 | 37 | case class From(table: String, joins: List[Join]) 38 | 39 | case class Select[Out]( 40 | cols: List[Col], 41 | from: From, 42 | where: Option[WhereCond], 43 | orderBy: Option[OrderBy], 44 | limit: Option[Int], 45 | offset: Option[Int] 46 | ) 47 | 48 | case class Delete(table: String, where: Option[WhereCond]) 49 | 50 | case class Set(col: Col) 51 | case class Update(table: String, sets: List[Set], where: Option[WhereCond]) 52 | 53 | case class InsertInto(table: String, columns: List[Col]) 54 | 55 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/conds.scala: -------------------------------------------------------------------------------- 1 | package typed.sql 2 | 3 | sealed trait WhereCond extends Product with Serializable 4 | object WhereCond { 5 | final case class Eq[K, V, T](v: V) extends WhereCond 6 | final case class Less[K, V, T](v: V) extends WhereCond 7 | final case class LessOrEq[K, V, T](v: V) extends WhereCond 8 | final case class Gt[K, V, T](v: V) extends WhereCond 9 | final case class GtOrEq[K, V, T](v: V) extends WhereCond 10 | final case class Like[K, T](v: String) extends WhereCond 11 | final case class In[K, V, T](v: List[V]) extends WhereCond 12 | 13 | final case class And[A, B](a: A, b: B) extends WhereCond 14 | final case class Or[A, B](a: A, b: B) extends WhereCond 15 | } 16 | 17 | sealed trait JoinCond 18 | object JoinCond { 19 | 20 | final case class Eq[K1, V, T1, K2, T2]( 21 | c1: Column[K1, V, T1], 22 | c2: Column[K2, V, T2] 23 | ) extends JoinCond 24 | 25 | final case class And[A <: JoinCond, B <: JoinCond](a: A, b: B) extends JoinCond 26 | final case class Or[A <: JoinCond, B <: JoinCond](a: A, b: B) extends JoinCond 27 | } 28 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/internal/FSHOps.scala: -------------------------------------------------------------------------------- 1 | package typed.sql.internal 2 | 3 | import shapeless._ 4 | import shapeless.ops.record.Selector 5 | import typed.sql._ 6 | 7 | import scala.annotation.implicitNotFound 8 | 9 | object FSHOps { 10 | 11 | trait IsFieldOf[A <: FSH, Name] 12 | 13 | object IsFieldOf { 14 | 15 | implicit def forFrom[N, N2, a, rs <: HList, ru <: HList]( 16 | implicit 17 | ev: N =:= N2 18 | ): IsFieldOf[From[TRepr[a, N, rs, ru]], N2] = null 19 | 20 | implicit def forIJ[N, N2, a, r <: HList, ru <: HList, c <: JoinCond, t <: FSH]( 21 | implicit ev: N =:= N2 22 | ): IsFieldOf[IJ[TRepr[a, N, r, ru], c, t], N2] = null 23 | 24 | implicit def forIJRecurse[T <: FSH, a, n2, rs <: HList, ru <: HList, N, R, c <: JoinCond]( 25 | implicit next: IsFieldOf[T, N] 26 | ): IsFieldOf[IJ[TRepr[a, n2, rs, ru], c, T], N] = null 27 | 28 | implicit def forLJ[N, N2, a, rs <: HList, ru <: HList, c <: JoinCond, t <: FSH]( 29 | implicit ev: N =:= N2 30 | ): IsFieldOf[LJ[TRepr[a, N, rs, ru], c, t], N2] = null 31 | 32 | implicit def forLJRecurse[T <: FSH, a, n2, rs <: HList, ru <: HList, N, R, c <: JoinCond]( 33 | implicit next: IsFieldOf[T, N] 34 | ): IsFieldOf[LJ[TRepr[a, n2, rs, ru], c, T], N] = null 35 | 36 | implicit def forRJ[N, N2, a, rs <: HList, ru <: HList, c <: JoinCond, t <: FSH]( 37 | implicit ev: N =:= N2 38 | ): IsFieldOf[RJ[TRepr[a, N, rs, ru], c, t], N2] = null 39 | 40 | implicit def forRJRecurse[T <: FSH, a, n2, rs <: HList, ru <: HList, N, R, c <: JoinCond]( 41 | implicit next: IsFieldOf[T, N] 42 | ): IsFieldOf[RJ[TRepr[a, n2, rs, ru], c, T], N] = null 43 | 44 | implicit def forFJ[N, N2, a, rs <: HList, ru <: HList, c <: JoinCond, t <: FSH]( 45 | implicit ev: N =:= N2 46 | ): IsFieldOf[FJ[TRepr[a, N, rs, ru], c, t], N2] = null 47 | 48 | implicit def forFJRecurse[T <: FSH, a, n2, rs <: HList, ru <: HList, N, R, c <: JoinCond]( 49 | implicit next: IsFieldOf[T, N] 50 | ): IsFieldOf[FJ[TRepr[a, n2, rs, ru], c, T], N] = null 51 | 52 | } 53 | 54 | @implicitNotFound("\nCan't prove that join operation is possible \n${A} \n\twith \n\t${B} \n\tusing\n\t${C}. \nCheck that you use columns that belongs to joining tables") 55 | trait CanJoin[A <: FSH, B <: From[_], C <: JoinCond] 56 | object CanJoin { 57 | 58 | implicit def canJoinEq1[A <: FSH, R <: TR, B <: From[_], T1, T2, k1, k2, v]( 59 | implicit 60 | f1: IsFieldOf[A, T1], 61 | f2: IsFieldOf[B, T2] 62 | ): CanJoin[A, B, JoinCond.Eq[k1, v, T1, k2, T2]] = null 63 | 64 | implicit def canJoinEq2[A <: FSH, R <: TR, B <: From[_], T1, T2, k1, k2, v]( 65 | implicit 66 | f1: IsFieldOf[A, T2], 67 | f2: IsFieldOf[B, T1] 68 | ): CanJoin[A, B, JoinCond.Eq[k1, v, T1, k2, T2]] = null 69 | } 70 | 71 | 72 | trait AllColumns[A <: FSH] { 73 | def columns: List[ast.Col] 74 | } 75 | 76 | object AllColumns { 77 | 78 | trait FromRepr[T <: TR] { 79 | def columns: List[ast.Col] 80 | } 81 | object FromRepr { 82 | 83 | implicit def fromRepr[a, N, Rs <: HList, ru <: HList]( 84 | implicit 85 | fieldNames: FieldNames[Rs], 86 | wt: Witness.Aux[N], 87 | ev: N <:< Symbol 88 | ): FromRepr[TRepr[a, N, Rs, ru]] = { 89 | new FromRepr[TRepr[a, N, Rs, ru]] { 90 | override def columns: List[ast.Col] = { 91 | val table = wt.value.name 92 | fieldNames().map(n => ast.Col(table, n)) 93 | } 94 | } 95 | 96 | } 97 | } 98 | 99 | private def create[A <: FSH](f: => List[ast.Col]): AllColumns[A] = new AllColumns[A] { 100 | override def columns: List[ast.Col] = f 101 | } 102 | 103 | implicit def forFrom[T <: TR]( 104 | implicit fromRepr: FromRepr[T] 105 | ): AllColumns[From[T]] = create(fromRepr.columns) 106 | 107 | implicit def forIJ[T <: TR, c <: JoinCond, tail <: FSH]( 108 | implicit 109 | fromRepr1: FromRepr[T], 110 | rest: AllColumns[tail] 111 | ): AllColumns[IJ[T, c, tail]] = create(rest.columns ++ fromRepr1.columns) 112 | 113 | implicit def forLJ[T <: TR, c <: JoinCond, tail <: FSH]( 114 | implicit 115 | fromRepr1: FromRepr[T], 116 | rest: AllColumns[tail] 117 | ): AllColumns[LJ[T, c, tail]] = create(rest.columns ++ fromRepr1.columns) 118 | 119 | implicit def forRJ[T <: TR, c <: JoinCond, tail <: FSH]( 120 | implicit 121 | fromRepr1: FromRepr[T], 122 | rest: AllColumns[tail] 123 | ): AllColumns[RJ[T, c, tail]] = create(rest.columns ++ fromRepr1.columns) 124 | 125 | implicit def forFJ[T <: TR, c <: JoinCond, tail <: FSH]( 126 | implicit 127 | fromRepr1: FromRepr[T], 128 | rest: AllColumns[tail] 129 | ): AllColumns[FJ[T, c, tail]] = create(rest.columns ++ fromRepr1.columns) 130 | } 131 | 132 | 133 | trait JoinCondInfer[C <: JoinCond] { 134 | def mkAst(): ast.JoinCond 135 | } 136 | 137 | // TODO AND ? OR? 138 | object JoinCondInfer { 139 | 140 | implicit def forEq[K1, v, T1, K2, T2]( 141 | implicit 142 | wt1: Witness.Aux[K1], 143 | ev1: K1 <:< Symbol, 144 | wt2: Witness.Aux[T1], 145 | ev2: T1 <:< Symbol, 146 | wt3: Witness.Aux[K2], 147 | ev3: K2 <:< Symbol, 148 | wt4: Witness.Aux[T2], 149 | ev4: T2 <:< Symbol 150 | ): JoinCondInfer[JoinCond.Eq[K1, v, T1, K2, T2]] = { 151 | new JoinCondInfer[JoinCond.Eq[K1, v, T1, K2, T2]] { 152 | override def mkAst(): ast.JoinCond = 153 | ast.JoinCondEq(ast.Col(wt2.value.name, wt1.value.name), ast.Col(wt4.value.name, wt3.value.name)) 154 | } 155 | } 156 | } 157 | 158 | trait SelectField[A <: FSH, K] { 159 | type Out 160 | } 161 | 162 | object SelectField { 163 | type Aux[A <: FSH, K, Out0] = SelectField[A, K] { type Out = Out0 } 164 | 165 | implicit def from[a, n, Rs <: HList, ru <: HList, K, V]( 166 | implicit 167 | sel: Selector.Aux[Rs, K, V] 168 | ): Aux[From[TRepr[a, n, Rs, ru]], K, V] = null 169 | 170 | implicit def ij1[a, n, Rs <: HList, ru <: HList, K, V, c <: JoinCond, tail <: FSH]( 171 | implicit 172 | sel: Selector.Aux[Rs, K, V] 173 | ): Aux[IJ[TRepr[a, n, Rs, ru], c, tail], K, V] = null 174 | 175 | implicit def ij2[a, n, Rs <: HList, ru <: HList, K, V, c <: JoinCond, TAIL <: FSH, O]( 176 | implicit 177 | next: SelectField.Aux[TAIL, K, O] 178 | ): Aux[IJ[TRepr[a, n, Rs, ru], c, TAIL], K, O] = null 179 | 180 | implicit def lj1[a, n, Rs <: HList, ru <: HList, K, V, c <: JoinCond, tail <: FSH, O]( 181 | implicit 182 | sel: Selector.Aux[Rs, K, V], 183 | fl: FlattenOption.Aux[V, O] 184 | ): Aux[LJ[TRepr[a, n, Rs, ru], c, tail], K, O] = null 185 | 186 | implicit def lj2[a, n, Rs <: HList, ru <: HList, K, V, c <: JoinCond, TAIL <: FSH, O]( 187 | implicit 188 | next: SelectField.Aux[TAIL, K, O] 189 | ): Aux[LJ[TRepr[a, n, Rs, ru], c, TAIL], K, O] = null 190 | 191 | implicit def rj1[a, n, Rs <: HList, ru <: HList, K, V, c <: JoinCond, tail <: FSH]( 192 | implicit 193 | sel: Selector.Aux[Rs, K, V] 194 | ): Aux[RJ[TRepr[a, n, Rs, ru], c, tail], K, V] = null 195 | 196 | implicit def rj2[a, n, Rs <: HList, ru <: HList, K, V, c <: JoinCond, TAIL <: FSH, O, O2]( 197 | implicit 198 | next: SelectField.Aux[TAIL, K, O], 199 | fl: FlattenOption.Aux[O, O2] 200 | ): Aux[RJ[TRepr[a, n, Rs, ru], c, TAIL], K, O2] = null 201 | 202 | implicit def fj1[a, n, Rs <: HList, ru <: HList, K, V, c <: JoinCond, tail <: FSH, O]( 203 | implicit 204 | sel: Selector.Aux[Rs, K, V], 205 | fl: FlattenOption.Aux[V, O] 206 | ): Aux[FJ[TRepr[a, n, Rs, ru], c, tail], K, O] = null 207 | 208 | implicit def fj2[a, n, Rs <: HList, ru <: HList, K, V, c <: JoinCond, TAIL <: FSH, O]( 209 | implicit 210 | next: SelectField.Aux[TAIL, K, O], 211 | fl: FlattenOption.Aux[V, O] 212 | ): Aux[FJ[TRepr[a, n, Rs, ru], c, TAIL], K, O] = null 213 | } 214 | 215 | 216 | trait LowLevelSelectInfer[A <: FSH, Q] { 217 | type Out <: HList 218 | def cols: List[ast.Col] 219 | } 220 | 221 | object LowLevelSelectInfer { 222 | type Aux[A <: FSH, Q, Out0] = LowLevelSelectInfer[A, Q] { type Out = Out0 } 223 | 224 | implicit def hnil[A <: FSH]: Aux[A, HNil, HNil] = { 225 | new LowLevelSelectInfer[A, HNil] { 226 | type Out = HNil 227 | def cols: List[ast.Col] = List.empty 228 | } 229 | } 230 | 231 | implicit def hCons[A <: FSH, TName, K, v, T <: HList, O <: HList, X]( 232 | implicit 233 | sel: SelectField.Aux[A, K, X], 234 | next: LowLevelSelectInfer.Aux[A, T, O], 235 | w1: Witness.Aux[TName], 236 | ev1: TName <:< Symbol, 237 | w2: Witness.Aux[K], 238 | ev2: K <:< Symbol 239 | ): Aux[A, Column[K, v, TName] :: T, X :: O] = { 240 | new LowLevelSelectInfer[A, Column[K, v, TName] :: T] { 241 | type Out = X :: O 242 | def cols: List[ast.Col] = ast.Col(w1.value.name, w2.value.name) :: next.cols 243 | } 244 | } 245 | 246 | } 247 | 248 | trait FromInfer[A <: FSH] { 249 | def mkAst(shape: A): ast.From 250 | } 251 | object FromInfer { 252 | 253 | private def create[A <: FSH](f: A => ast.From): FromInfer[A] = new FromInfer[A] { 254 | override def mkAst(shape: A): ast.From = f(shape) 255 | } 256 | 257 | implicit def from[a, N, rs <: HList, ru <: HList]( 258 | implicit 259 | wt: Witness.Aux[N], 260 | ev: N <:< Symbol 261 | ): FromInfer[From[TRepr[a, N, rs, ru]]] = create(_ => ast.From(wt.value.name, List.empty)) 262 | 263 | implicit def ij[a, N, rs <: HList, ru <: HList, C <: JoinCond, TAIL <: FSH]( 264 | implicit 265 | wt: Witness.Aux[N], 266 | ev: N <:< Symbol, 267 | cndInf: JoinCondInfer[C], 268 | tInf: FromInfer[TAIL] 269 | ): FromInfer[IJ[TRepr[a, N, rs, ru], C, TAIL]] = { 270 | 271 | create(a => { 272 | val x = tInf.mkAst(a.tail) 273 | val j = ast.InnerJoin(wt.value.name, cndInf.mkAst()) 274 | ast.From(x.table, x.joins :+ j) 275 | }) 276 | } 277 | 278 | implicit def lj[a, N, rs <: HList, ru <: HList, C <: JoinCond, TAIL <: FSH]( 279 | implicit 280 | wt: Witness.Aux[N], 281 | ev: N <:< Symbol, 282 | cndInf: JoinCondInfer[C], 283 | tInf: FromInfer[TAIL] 284 | ): FromInfer[LJ[TRepr[a, N, rs, ru], C, TAIL]] = { 285 | 286 | create(a => { 287 | val x = tInf.mkAst(a.tail) 288 | val j = ast.LeftJoin(wt.value.name, cndInf.mkAst()) 289 | ast.From(x.table, x.joins :+ j) 290 | }) 291 | } 292 | 293 | implicit def rj[a, N, rs <: HList, ru <: HList , C <: JoinCond, TAIL <: FSH]( 294 | implicit 295 | wt: Witness.Aux[N], 296 | ev: N <:< Symbol, 297 | cndInf: JoinCondInfer[C], 298 | tInf: FromInfer[TAIL] 299 | ): FromInfer[RJ[TRepr[a, N, rs, ru], C, TAIL]] = { 300 | 301 | create(a => { 302 | val x = tInf.mkAst(a.tail) 303 | val j = ast.RightJoin(wt.value.name, cndInf.mkAst()) 304 | ast.From(x.table, x.joins :+ j) 305 | }) 306 | } 307 | 308 | implicit def fj[a, N, rs <: HList, ru <: HList, C <: JoinCond, TAIL <: FSH]( 309 | implicit 310 | wt: Witness.Aux[N], 311 | ev: N <:< Symbol, 312 | cndInf: JoinCondInfer[C], 313 | tInf: FromInfer[TAIL] 314 | ): FromInfer[FJ[TRepr[a, N, rs, ru], C, TAIL]] = { 315 | 316 | create(a => { 317 | val x = tInf.mkAst(a.tail) 318 | val j = ast.FullJoin(wt.value.name, cndInf.mkAst()) 319 | ast.From(x.table, x.joins :+ j) 320 | }) 321 | } 322 | 323 | } 324 | 325 | 326 | trait FromInferForStarSelect[A <: FSH] { 327 | type Out 328 | def mkAst(shape: A): ast.From 329 | } 330 | 331 | //TODO: flatten tuple, flatten option for all join types 332 | object FromInferForStarSelect { 333 | type Aux[A <: FSH, Out0] = FromInferForStarSelect[A] { type Out = Out0 } 334 | 335 | implicit def starFrom[A, N, rs <: HList, ru <: HList]( 336 | implicit 337 | wt: Witness.Aux[N], 338 | ev: N <:< Symbol 339 | ):Aux[From[TRepr[A, N, rs, ru]], A] = { 340 | new FromInferForStarSelect[From[TRepr[A, N, rs, ru]]] { 341 | type Out = A 342 | def mkAst(shape: From[TRepr[A, N, rs, ru]]): ast.From = ast.From(wt.value.name, List.empty) 343 | } 344 | } 345 | 346 | implicit def starIJ[A, N, rs <: HList, ru <: HList, A2, N2, r2 <: HList, C <: JoinCond, tail <: FSH, O1, O2]( 347 | implicit 348 | wt: Witness.Aux[N], 349 | ev: N <:< Symbol, 350 | cndInf: JoinCondInfer[C], 351 | tInf: FromInferForStarSelect.Aux[tail, O1], 352 | flTuple: TupleAppend.Aux[O1, A, O2] 353 | ):Aux[IJ[TRepr[A, N, rs, ru], C, tail], O2] = { 354 | new FromInferForStarSelect[IJ[TRepr[A, N, rs, ru], C, tail]] { 355 | type Out = O2 356 | def mkAst(shape: IJ[TRepr[A, N, rs, ru], C, tail]): ast.From = { 357 | val x = tInf.mkAst(shape.tail) 358 | val j = ast.InnerJoin(wt.value.name, cndInf.mkAst()) 359 | ast.From(x.table, x.joins :+ j) 360 | } 361 | } 362 | } 363 | 364 | implicit def starLJ[A, N, rs <: HList, ru <: HList, A2, N2, r2 <: HList, C <: JoinCond, tail <: FSH, O1, O2]( 365 | implicit 366 | wt: Witness.Aux[N], 367 | ev: N <:< Symbol, 368 | cndInf: JoinCondInfer[C], 369 | tInf: FromInferForStarSelect.Aux[tail, O1], 370 | flTuple: TupleAppend.Aux[O1, Option[A], O2] 371 | ):Aux[LJ[TRepr[A, N, rs, ru], C, tail], O2] = { 372 | new FromInferForStarSelect[LJ[TRepr[A, N, rs, ru], C, tail]] { 373 | type Out = O2 374 | def mkAst(shape: LJ[TRepr[A, N, rs, ru], C, tail]): ast.From = { 375 | val x = tInf.mkAst(shape.tail) 376 | val j = ast.LeftJoin(wt.value.name, cndInf.mkAst()) 377 | ast.From(x.table, x.joins :+ j) 378 | } 379 | } 380 | } 381 | 382 | implicit def starRJ[A, N, rs <: HList, ru <: HList, A2, N2, r2 <: HList, C <: JoinCond, tail <: FSH, O1]( 383 | implicit 384 | wt: Witness.Aux[N], 385 | ev: N <:< Symbol, 386 | cndInf: JoinCondInfer[C], 387 | tInf: FromInferForStarSelect.Aux[tail, O1] 388 | ):Aux[RJ[TRepr[A, N, rs, ru], C, tail], (Option[O1], A)] = { 389 | new FromInferForStarSelect[RJ[TRepr[A, N, rs, ru], C, tail]] { 390 | type Out = (Option[O1], A) 391 | def mkAst(shape: RJ[TRepr[A, N, rs, ru], C, tail]): ast.From = { 392 | val x = tInf.mkAst(shape.tail) 393 | val j = ast.RightJoin(wt.value.name, cndInf.mkAst()) 394 | ast.From(x.table, x.joins :+ j) 395 | } 396 | } 397 | } 398 | 399 | implicit def starFJ[A, N, rs <: HList, ru <: HList, A2, N2, r2 <: HList, C <: JoinCond, tail <: FSH, O1]( 400 | implicit 401 | wt: Witness.Aux[N], 402 | ev: N <:< Symbol, 403 | cndInf: JoinCondInfer[C], 404 | tInf: FromInferForStarSelect.Aux[tail, O1] 405 | ):Aux[FJ[TRepr[A, N, rs, ru], C, tail], (Option[O1], Option[A])] = { 406 | new FromInferForStarSelect[FJ[TRepr[A, N, rs, ru], C, tail]] { 407 | type Out = (Option[O1], Option[A]) 408 | def mkAst(shape: FJ[TRepr[A, N, rs, ru], C, tail]): ast.From = { 409 | val x = tInf.mkAst(shape.tail) 410 | val j = ast.FullJoin(wt.value.name, cndInf.mkAst()) 411 | ast.From(x.table, x.joins :+ j) 412 | } 413 | } 414 | } 415 | } 416 | 417 | 418 | } 419 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/internal/FieldNames.scala: -------------------------------------------------------------------------------- 1 | package typed.sql.internal 2 | 3 | import shapeless.labelled.FieldType 4 | import shapeless.{HList, HNil, Witness, _} 5 | 6 | trait FieldNames[A <: HList] { 7 | def apply(): List[String] 8 | } 9 | 10 | object FieldNames { 11 | 12 | implicit val hNil: FieldNames[HNil] = new FieldNames[HNil] { 13 | override def apply(): List[String] = List.empty 14 | } 15 | 16 | implicit def hCons[T <: HList, K, V]( 17 | implicit 18 | wit: Witness.Aux[K], 19 | ev2: K <:< Symbol, 20 | next: FieldNames[T] 21 | ): FieldNames[FieldType[K, V] :: T] = { 22 | new FieldNames[FieldType[K, V] :: T] { 23 | override def apply(): List[String] = wit.value.name :: next() 24 | } 25 | } 26 | 27 | def apply[A <: HList](implicit fN: FieldNames[A]): FieldNames[A] = fN 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/internal/FlattenOption.scala: -------------------------------------------------------------------------------- 1 | package typed.sql.internal 2 | 3 | trait FlattenOption[A] { 4 | type Out 5 | } 6 | 7 | trait LowPrioFlattenOption { 8 | 9 | type Aux[A, Out0] = FlattenOption[A] { type Out = Out0 } 10 | 11 | implicit def forA[A]: Aux[A, Option[A]] = null 12 | } 13 | 14 | object FlattenOption extends LowPrioFlattenOption { 15 | 16 | implicit def forOpt[A]: Aux[Option[A], Option[A]] = null 17 | } 18 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/internal/InsertValuesInfer.scala: -------------------------------------------------------------------------------- 1 | package typed.sql.internal 2 | 3 | import shapeless._ 4 | import shapeless.labelled.FieldType 5 | import shapeless.ops.record.Values 6 | import typed.sql.{TR, ast} 7 | 8 | trait InsertValuesInfer[A, V] { 9 | def columns: List[String] 10 | } 11 | 12 | trait CheckHTypes[A, B] 13 | object CheckHTypes { 14 | implicit val hNil: CheckHTypes[HNil, HNil] = null 15 | implicit def hCons[H1, H2, T1 <: HList,T2 <: HList]( 16 | implicit ev: H2 <:< H1 17 | ): CheckHTypes[H1 :: T1, H2 :: T2] = null 18 | } 19 | 20 | object InsertValuesInfer { 21 | 22 | implicit def labelledRepr[A <: HList, Vs <: HList, In <: HList]( 23 | implicit 24 | vs: Values.Aux[A, Vs], 25 | check: CheckHTypes[Vs, In], 26 | fieldNames: FieldNames[A] 27 | ): InsertValuesInfer[A, In] = { 28 | new InsertValuesInfer[A, In] { 29 | val columns = fieldNames() 30 | } 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/internal/MkWrite.scala: -------------------------------------------------------------------------------- 1 | package typed.sql.internal 2 | 3 | import doobie.enum.Nullability.NoNulls 4 | import doobie.util.param.Param 5 | import doobie.util.{Put, Write} 6 | import shapeless._ 7 | 8 | trait MkWrite[A] { 9 | def apply(a: A): Write[A] 10 | } 11 | 12 | object MkWrite { 13 | 14 | implicit def fromParam[A](implicit p: Param[A]): MkWrite[A] = 15 | new MkWrite[A] { 16 | def apply(a: A): Write[A] = p.write 17 | } 18 | 19 | implicit def list[A](implicit P: Put[A]): MkWrite[List[A]] = { 20 | new MkWrite[List[A]] { 21 | override def apply(a: List[A]): Write[List[A]] = { 22 | new Write[List[A]]( 23 | a.map(_ => P -> NoNulls), 24 | a => a, 25 | (ps, n, a) => { 26 | a.indices.foreach(i => { 27 | P.unsafeSetNonNullable(ps, n + i, a(i)) 28 | }) 29 | }, 30 | (rs, n, a) => { 31 | a.indices.foreach(i => { 32 | P.unsafeUpdateNonNullable(rs, n + i, a(i)) 33 | }) 34 | } 35 | ) 36 | } 37 | } 38 | } 39 | 40 | implicit def hnil: MkWrite[HNil] = new MkWrite[HNil] { 41 | override def apply(a: HNil): Write[HNil] = Write.emptyProduct 42 | } 43 | 44 | implicit def hcons[H, T <: HList]( 45 | implicit 46 | H: Lazy[MkWrite[H]], 47 | T: Lazy[MkWrite[T]] 48 | ): MkWrite[H :: T] = { 49 | new MkWrite[H :: T] { 50 | override def apply(a: H :: T): Write[H :: T] = { 51 | val w1 = H.value(a.head) 52 | val w2 = T.value(a.tail) 53 | 54 | new Write( 55 | w1.puts ++ w2.puts, 56 | { case h :: t => w1.toList(h) ++ w2.toList(t) }, 57 | { case (ps, n, h :: t) => w1.unsafeSet(ps, n, h); w2.unsafeSet(ps, n + w1.length, t) }, 58 | { case (rs, n, h :: t) => w1.unsafeUpdate(rs, n, h); w2.unsafeUpdate(rs, n + w1.length, t) } 59 | ) 60 | } 61 | } 62 | } 63 | } 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/internal/OrderByInfer.scala: -------------------------------------------------------------------------------- 1 | package typed.sql.internal 2 | 3 | import shapeless._ 4 | import shapeless.ops.hlist.IsHCons 5 | import typed.sql.internal.FSHOps.IsFieldOf 6 | import typed.sql._ 7 | 8 | trait OrderByInfer[A <: FSH, Q] { 9 | def columns: List[(ast.Col, ast.SortOrder)] 10 | } 11 | 12 | object OrderByInfer { 13 | 14 | implicit def lastClmn[A <: FSH, K, v, N]( 15 | implicit 16 | isFieldOf: IsFieldOf[A, N], 17 | wt1: Witness.Aux[N], 18 | ev1: N <:< Symbol, 19 | wt2: Witness.Aux[K], 20 | ev2: K <:< Symbol 21 | ): OrderByInfer[A, Column[K, v, N] :: HNil] = 22 | new OrderByInfer[A, Column[K, v, N] :: HNil] { 23 | val columns: List[(ast.Col, ast.SortOrder)] = List(ast.Col(wt1.value.name, wt2.value.name) -> ast.ASC) 24 | } 25 | 26 | implicit def lastASC[A <: FSH, K, v, N]( 27 | implicit 28 | isFieldOf: IsFieldOf[A, N], 29 | wt1: Witness.Aux[N], 30 | ev1: N <:< Symbol, 31 | wt2: Witness.Aux[K], 32 | ev2: K <:< Symbol 33 | ): OrderByInfer[A, ASC[K, v, N] :: HNil] = 34 | new OrderByInfer[A, ASC[K, v, N] :: HNil] { 35 | val columns: List[(ast.Col, ast.SortOrder)] = List(ast.Col(wt1.value.name, wt2.value.name) -> ast.ASC) 36 | } 37 | 38 | implicit def lastDESC[A <: FSH, K, v, N]( 39 | implicit 40 | isFieldOf: IsFieldOf[A, N], 41 | wt1: Witness.Aux[N], 42 | ev1: N <:< Symbol, 43 | wt2: Witness.Aux[K], 44 | ev2: K <:< Symbol 45 | ): OrderByInfer[A, DESC[K, v, N] :: HNil] = 46 | new OrderByInfer[A, DESC[K, v, N] :: HNil] { 47 | val columns: List[(ast.Col, ast.SortOrder)] = List(ast.Col(wt1.value.name, wt2.value.name) -> ast.DESC) 48 | } 49 | 50 | implicit def hConsClmn[A <: FSH,T <: HList, K, v, N]( 51 | implicit 52 | isFieldOf: IsFieldOf[A, N], 53 | isHCons1: IsHCons[T], 54 | next: OrderByInfer[A, T], 55 | wt1: Witness.Aux[N], 56 | ev1: N <:< Symbol, 57 | wt2: Witness.Aux[K], 58 | ev2: K <:< Symbol 59 | ): OrderByInfer[A, Column[K, v, N] :: T] = 60 | new OrderByInfer[A, Column[K, v, N] :: T] { 61 | val columns: List[(ast.Col, ast.SortOrder)] = ast.Col(wt1.value.name, wt2.value.name) -> ast.ASC :: next.columns 62 | } 63 | 64 | implicit def hConsASC[A <: FSH,T <: HList, K, v, N]( 65 | implicit 66 | isFieldOf: IsFieldOf[A, N], 67 | isHCons1: IsHCons[T], 68 | next: OrderByInfer[A, T], 69 | wt1: Witness.Aux[N], 70 | ev1: N <:< Symbol, 71 | wt2: Witness.Aux[K], 72 | ev2: K <:< Symbol 73 | ): OrderByInfer[A, ASC[K, v, N] :: T] = 74 | new OrderByInfer[A, ASC[K, v, N] :: T] { 75 | val columns: List[(ast.Col, ast.SortOrder)] = ast.Col(wt1.value.name, wt2.value.name) -> ast.ASC :: next.columns 76 | } 77 | 78 | implicit def hConsDESC[A <: FSH,T <: HList, K, v, N]( 79 | implicit 80 | isFieldOf: IsFieldOf[A, N], 81 | isHCons1: IsHCons[T], 82 | next: OrderByInfer[A, T], 83 | wt1: Witness.Aux[N], 84 | ev1: N <:< Symbol, 85 | wt2: Witness.Aux[K], 86 | ev2: K <:< Symbol 87 | ): OrderByInfer[A, DESC[K, v, N] :: T] = 88 | new OrderByInfer[A, DESC[K, v, N] :: T] { 89 | val columns: List[(ast.Col, ast.SortOrder)] = ast.Col(wt1.value.name, wt2.value.name) -> ast.DESC :: next.columns 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/internal/RemoveFields.scala: -------------------------------------------------------------------------------- 1 | package typed.sql.internal 2 | 3 | import shapeless._ 4 | import shapeless.labelled.FieldType 5 | import shapeless.ops.record.Remover 6 | 7 | /** 8 | * A - HList of FieldTypes 9 | * B - HList of symbols 10 | */ 11 | trait RemoveFields[A <: HList, B <: HList] { 12 | type Out1 <: HList 13 | } 14 | 15 | trait LowPrioRemoveFields { 16 | 17 | type Aux[A <: HList, B <: HList, Out0 <: HList] = RemoveFields[A, B] {type Out = Out0 } 18 | 19 | } 20 | 21 | object RemoveFields extends LowPrioRemoveFields { 22 | 23 | implicit def hnil[A <: HList]: Aux[A, HNil, A] = null 24 | 25 | implicit def hCons1[A <: HList, H, T2 <: HList, K, v, ROut <: HList, TOut <: HList]( 26 | implicit 27 | remover: Remover.Aux[A, H, (v, ROut)], 28 | next: RemoveFields.Aux[ROut, T2, TOut] 29 | ): Aux[A, H :: T2, TOut] = { 30 | // println(s"DROP ${wt.value.name}") 31 | null 32 | } 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/internal/Repr2Ops.scala: -------------------------------------------------------------------------------- 1 | package typed.sql.internal 2 | 3 | import shapeless.ops.record.Selector 4 | import shapeless._ 5 | import typed.sql.{Assign, ast} 6 | 7 | object Repr2Ops { 8 | 9 | trait InferUpdateSet[R <: HList, In <: HList] { 10 | type Out 11 | def mkAst: List[ast.Set] 12 | def out(in: In): Out 13 | } 14 | 15 | object InferUpdateSet { 16 | 17 | type Aux[R <: HList, In <: HList, Out0] = InferUpdateSet[R, In] { type Out = Out0 } 18 | 19 | implicit def hnil[R <: HList]: Aux[R, HNil, HNil] = 20 | new InferUpdateSet[R, HNil] { 21 | type Out = HNil 22 | def mkAst: List[ast.Set] = List.empty 23 | def out(in: HNil): HNil = HNil 24 | } 25 | 26 | implicit def hCons[R <: HList, K, V, N, T <: HList, Out1 <: HList]( 27 | implicit 28 | sel: Selector.Aux[R, K, V], 29 | next: InferUpdateSet.Aux[R, T, Out1], 30 | wt1: Witness.Aux[K], 31 | ev1: K <:< Symbol, 32 | wt2: Witness.Aux[N], 33 | ev2: N <:< Symbol 34 | ): Aux[R, Assign[K, V, N] :: T, V :: Out1] = 35 | new InferUpdateSet[R, Assign[K, V, N] :: T] { 36 | type Out = V :: Out1 37 | def mkAst: List[ast.Set] = ast.Set(ast.Col(wt2.value.name, wt1.value.name)) :: next.mkAst 38 | def out(in: Assign[K, V, N] :: T): V :: Out1 = in.head.v :: next.out(in.tail) 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/internal/SelectInfer.scala: -------------------------------------------------------------------------------- 1 | package typed.sql.internal 2 | 3 | import shapeless._ 4 | import shapeless.ops.hlist.Tupler 5 | import typed.sql.internal.FSHOps.{FromInfer, FromInferForStarSelect, LowLevelSelectInfer} 6 | import typed.sql._ 7 | 8 | import scala.annotation.implicitNotFound 9 | 10 | @implicitNotFound("Couldn't infer selection:\n Given: ${A},\n Query: ${Q}.") 11 | trait SelectInfer[A <: FSH, Q] { 12 | type Out 13 | def mkAst(shape: A): ast.Select[Out] 14 | } 15 | 16 | trait LowPrioSelectInfer { 17 | @implicitNotFound("Couldn't infer selection\n Given: ${A},\n Query: ${Q}.") 18 | type Aux[A <: FSH, Q, Out0] = SelectInfer[A, Q] { type Out = Out0 } 19 | 20 | implicit def columns[A <: FSH, H <: HList, O <: HList]( 21 | implicit 22 | llsi: LowLevelSelectInfer.Aux[A, H, O], 23 | fInf: FromInfer[A] 24 | ): Aux[A, H, O] = { 25 | new SelectInfer[A, H] { 26 | type Out = O 27 | override def mkAst(shape: A): ast.Select[O] = { 28 | ast.Select(llsi.cols, fInf.mkAst(shape), None, None, None, None) 29 | } 30 | } 31 | } 32 | } 33 | 34 | object SelectInfer extends LowPrioSelectInfer { 35 | 36 | implicit def forStar[A <: FSH, O]( 37 | implicit 38 | allC: FSHOps.AllColumns[A], 39 | fromInf: FromInferForStarSelect.Aux[A, O] 40 | ): Aux[A, All.type :: HNil, O] = { 41 | new SelectInfer[A, All.type :: HNil] { 42 | type Out = O 43 | def mkAst(shape: A): ast.Select[O] = { 44 | ast.Select(allC.columns, fromInf.mkAst(shape), None, None, None, None) 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/internal/TupleAppend.scala: -------------------------------------------------------------------------------- 1 | package typed.sql.internal 2 | 3 | trait TupleAppend[A, B] { 4 | type Out 5 | } 6 | 7 | trait LowPrioTupleAppend { 8 | type Aux[A, B, Out0] = TupleAppend[A, B] { type Out = Out0 } 9 | 10 | implicit def forA[A, B]: Aux[A, B, (A, B)] = null 11 | } 12 | 13 | object TupleAppend extends TupleAppendInstances 14 | 15 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/internal/WhereInfer.scala: -------------------------------------------------------------------------------- 1 | package typed.sql.internal 2 | 3 | import cats.data.NonEmptyList 4 | import shapeless._ 5 | import shapeless.ops.adjoin.Adjoin 6 | import typed.sql.internal.FSHOps.IsFieldOf 7 | import typed.sql.{FSH, WhereCond, ast} 8 | 9 | import scala.reflect.ClassTag 10 | 11 | trait WhereInfer[A <: FSH, C] { 12 | type Out 13 | def mkAst(c: C): ast.WhereCond 14 | def out(c: C): Out 15 | } 16 | 17 | object WhereInfer { 18 | 19 | type Aux[A <: FSH, C, Out0] = WhereInfer[A, C] { type Out = Out0 } 20 | 21 | implicit def forEq[A <: FSH, K, V, T]( 22 | implicit 23 | wt1: Witness.Aux[T], 24 | ev1: T <:< Symbol, 25 | wt2: Witness.Aux[K], 26 | ev2: K <:< Symbol, 27 | evev: IsFieldOf[A, T] 28 | ): Aux[A, WhereCond.Eq[K, V, T], V :: HNil] = { 29 | new WhereInfer[A, WhereCond.Eq[K, V, T]] { 30 | type Out = V :: HNil 31 | def mkAst(c: WhereCond.Eq[K, V, T]): ast.WhereCond = ast.WhereEq(ast.Col(wt1.value.name, wt2.value.name)) 32 | def out(c: WhereCond.Eq[K, V, T]): V :: HNil = c.v :: HNil 33 | } 34 | } 35 | 36 | implicit def forLess[A <: FSH, K, V, T]( 37 | implicit 38 | wt1: Witness.Aux[T], 39 | ev1: T <:< Symbol, 40 | wt2: Witness.Aux[K], 41 | ev2: K <:< Symbol, 42 | evev: IsFieldOf[A, T] 43 | ): Aux[A, WhereCond.Less[K, V, T], V :: HNil] = { 44 | new WhereInfer[A, WhereCond.Less[K, V, T]] { 45 | type Out = V :: HNil 46 | def mkAst(c: WhereCond.Less[K, V, T]): ast.WhereCond = ast.Less(ast.Col(wt1.value.name, wt2.value.name)) 47 | def out(c: WhereCond.Less[K, V, T]): V :: HNil = c.v :: HNil 48 | } 49 | } 50 | 51 | implicit def forLessOrEq[A <: FSH, K, V, T]( 52 | implicit 53 | wt1: Witness.Aux[T], 54 | ev1: T <:< Symbol, 55 | wt2: Witness.Aux[K], 56 | ev2: K <:< Symbol, 57 | evev: IsFieldOf[A, T] 58 | ): Aux[A, WhereCond.LessOrEq[K, V, T], V :: HNil] = { 59 | new WhereInfer[A, WhereCond.LessOrEq[K, V, T]] { 60 | type Out = V :: HNil 61 | def mkAst(c: WhereCond.LessOrEq[K, V, T]): ast.WhereCond = ast.LessOrEq(ast.Col(wt1.value.name, wt2.value.name)) 62 | def out(c: WhereCond.LessOrEq[K, V, T]): V :: HNil = c.v :: HNil 63 | } 64 | } 65 | 66 | implicit def forGt[A <: FSH, K, V, T]( 67 | implicit 68 | wt1: Witness.Aux[T], 69 | ev1: T <:< Symbol, 70 | wt2: Witness.Aux[K], 71 | ev2: K <:< Symbol, 72 | evev: IsFieldOf[A, T] 73 | ): Aux[A, WhereCond.Gt[K, V, T], V :: HNil] = { 74 | new WhereInfer[A, WhereCond.Gt[K, V, T]] { 75 | type Out = V :: HNil 76 | def mkAst(c: WhereCond.Gt[K, V, T]): ast.WhereCond = ast.Gt(ast.Col(wt1.value.name, wt2.value.name)) 77 | def out(c: WhereCond.Gt[K, V, T]): V :: HNil = c.v :: HNil 78 | } 79 | } 80 | 81 | implicit def forGtOrEq[A <: FSH, K, V, T]( 82 | implicit 83 | wt1: Witness.Aux[T], 84 | ev1: T <:< Symbol, 85 | wt2: Witness.Aux[K], 86 | ev2: K <:< Symbol, 87 | evev: IsFieldOf[A, T] 88 | ): Aux[A, WhereCond.GtOrEq[K, V, T], V :: HNil] = { 89 | new WhereInfer[A, WhereCond.GtOrEq[K, V, T]] { 90 | type Out = V :: HNil 91 | def mkAst(c: WhereCond.GtOrEq[K, V, T]): ast.WhereCond = ast.GtOrEq(ast.Col(wt1.value.name, wt2.value.name)) 92 | def out(c: WhereCond.GtOrEq[K, V, T]): V :: HNil = c.v :: HNil 93 | } 94 | } 95 | 96 | implicit def forLike[A <: FSH, K, T]( 97 | implicit 98 | wt1: Witness.Aux[T], 99 | ev1: T <:< Symbol, 100 | wt2: Witness.Aux[K], 101 | ev2: K <:< Symbol, 102 | evev: IsFieldOf[A, T] 103 | ): Aux[A, WhereCond.Like[K, T], String :: HNil] = { 104 | new WhereInfer[A, WhereCond.Like[K, T]] { 105 | type Out = String :: HNil 106 | def mkAst(c: WhereCond.Like[K, T]): ast.WhereCond = ast.Like(ast.Col(wt1.value.name, wt2.value.name)) 107 | def out(c: WhereCond.Like[K, T]): String :: HNil = c.v :: HNil 108 | } 109 | } 110 | 111 | implicit def forIn[A <: FSH, K, V, T]( 112 | implicit 113 | wt1: Witness.Aux[T], 114 | ev1: T <:< Symbol, 115 | wt2: Witness.Aux[K], 116 | ev2: K <:< Symbol, 117 | evev: IsFieldOf[A, T], 118 | ct: ClassTag[V] 119 | ): Aux[A, WhereCond.In[K, V, T], List[V] :: HNil] = { 120 | new WhereInfer[A, WhereCond.In[K, V, T]] { 121 | type Out = List[V] :: HNil 122 | def mkAst(c: WhereCond.In[K, V, T]): ast.WhereCond = ast.In(ast.Col(wt1.value.name, wt2.value.name), c.v.size) 123 | def out(c: WhereCond.In[K, V, T]): List[V] :: HNil = c.v :: HNil 124 | } 125 | } 126 | 127 | implicit def forAnd[S <: FSH, A, B, AOut, BOut, Joined <: HList]( 128 | implicit 129 | aInf: WhereInfer.Aux[S, A, AOut], 130 | bInf: WhereInfer.Aux[S, B, BOut], 131 | adjoin: Adjoin.Aux[AOut :: BOut :: HNil, Joined] 132 | ): Aux[S, WhereCond.And[A, B], Joined] = { 133 | new WhereInfer[S, WhereCond.And[A, B]] { 134 | type Out = Joined 135 | def mkAst(c: WhereCond.And[A, B]): ast.WhereCond = ast.And(aInf.mkAst(c.a), bInf.mkAst(c.b)) 136 | def out(c: WhereCond.And[A, B]): Joined = adjoin(aInf.out(c.a) :: bInf.out(c.b) :: HNil) 137 | } 138 | } 139 | 140 | implicit def forOr[S <: FSH, A, B, AOut, BOut, Joined <: HList]( 141 | implicit 142 | aInf: WhereInfer.Aux[S, A, AOut], 143 | bInf: WhereInfer.Aux[S, B, BOut], 144 | adjoin: Adjoin.Aux[AOut :: BOut :: HNil, Joined] 145 | ): Aux[S, WhereCond.Or[A, B], Joined] = { 146 | new WhereInfer[S, WhereCond.Or[A, B]] { 147 | type Out = Joined 148 | def mkAst(c: WhereCond.Or[A, B]): ast.WhereCond = ast.Or(aInf.mkAst(c.a), bInf.mkAst(c.b)) 149 | def out(c: WhereCond.Or[A, B]): Joined = adjoin(aInf.out(c.a) :: bInf.out(c.b) :: HNil) 150 | } 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/prefixes/InsertIntoPrefix.scala: -------------------------------------------------------------------------------- 1 | package typed.sql.prefixes 2 | 3 | import shapeless._ 4 | import shapeless.ops.record.Values 5 | import typed.sql.{Insert, Table, ast} 6 | import typed.sql.internal.InsertValuesInfer 7 | 8 | class InsertIntoPrefix[A, N, Rs <: HList, Ru <: HList](table: Table[A, N, Rs, Ru]) { 9 | 10 | def values[In, In2 <: HList](vs: In)( 11 | implicit 12 | gen: Generic.Aux[In, In2], 13 | inferInsertValues: InsertValuesInfer[Ru, In2] 14 | ): Insert[In2] = { 15 | val n = table.name 16 | val cols = inferInsertValues.columns.map(v => ast.Col(n, v)) 17 | val in = gen.to(vs) 18 | Insert(ast.InsertInto(n, cols), in) 19 | } 20 | 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/prefixes/SelectionPrefix.scala: -------------------------------------------------------------------------------- 1 | package typed.sql.prefixes 2 | 3 | import shapeless.{HList, HNil} 4 | import typed.sql._ 5 | import typed.sql.internal.SelectInfer 6 | 7 | case class SelectionPrefix[Q](query: Q) { 8 | 9 | def from[S <: FSH, O](shape: S)( 10 | implicit 11 | inf: SelectInfer.Aux[S, Q, O] 12 | ): Select[S, O, HNil] = Select.create(inf.mkAst(shape)) 13 | 14 | def from[A, N, Rs <: HList, Ru <: HList, O](table: Table[A, N, Rs, Ru])( 15 | implicit 16 | inf: SelectInfer.Aux[From[TRepr[A, N, Rs, Ru]], Q, O] 17 | ): Select[From[TRepr[A, N, Rs, Ru]], O, HNil] = Select.create(inf.mkAst(From(table.repr))) 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/prefixes/UpdationPrefix.scala: -------------------------------------------------------------------------------- 1 | package typed.sql.prefixes 2 | 3 | import shapeless.{HList, ProductArgs, RecordArgs} 4 | import typed.sql.internal.Repr2Ops.InferUpdateSet 5 | import typed.sql._ 6 | 7 | class UpdationPrefix[A, N, Rs <: HList, Ru <: HList](table: Table[A, N, Rs, Ru]) { 8 | 9 | object set extends ProductArgs { 10 | def applyProduct[In <: HList, R <: HList](values: In)( 11 | implicit 12 | inferUpdateSet: InferUpdateSet.Aux[Ru, In, R] 13 | ): Update[From[TRepr[A, N, Rs, Ru]], R] = new Update[From[TRepr[A, N, Rs, Ru]], R] { 14 | override def astData: ast.Update = ast.Update(table.name, inferUpdateSet.mkAst, None) 15 | override def in: R = inferUpdateSet.out(values) 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/prefixes/joinPrefixes.scala: -------------------------------------------------------------------------------- 1 | package typed.sql.prefixes 2 | 3 | import shapeless.HList 4 | import typed.sql._ 5 | import typed.sql.internal.FSHOps.CanJoin 6 | 7 | class IJPrefix[A <: FSH, B, N, Rs <: HList, Ru <: HList](a: A, b: From[TRepr[B, N, Rs, Ru]]) { 8 | 9 | def on[C <: JoinCond](c: C)( 10 | implicit 11 | canJoin: CanJoin[A, From[TRepr[B, N, Rs, Ru]], C] 12 | ): IJ[TRepr[B, N, Rs, Ru], C, A] = IJ(b.repr, c, a) 13 | 14 | } 15 | 16 | class LJPrefix[A <: FSH, B, N, Rs <: HList, Ru <: HList](a: A, b: From[TRepr[B, N, Rs, Ru]]) { 17 | 18 | def on[C <: JoinCond](c: C)( 19 | implicit 20 | canJoin: CanJoin[A, From[TRepr[B, N, Rs, Ru]], C] 21 | ): LJ[TRepr[B, N, Rs, Ru], C, A] = LJ(b.repr, c, a) 22 | 23 | } 24 | 25 | class RJPrefix[A <: FSH, B, N, Rs <: HList, Ru <: HList](a: A, b: From[TRepr[B, N, Rs, Ru]]) { 26 | 27 | def on[C <: JoinCond](c: C)( 28 | implicit 29 | canJoin: CanJoin[A, From[TRepr[B, N, Rs, Ru]], C] 30 | ): RJ[TRepr[B, N, Rs, Ru], C, A] = RJ(b.repr, c, a) 31 | 32 | } 33 | 34 | class FJPrefix[A <: FSH, B, N, Rs <: HList, Ru <: HList](a: A, b: From[TRepr[B, N, Rs, Ru]]) { 35 | 36 | def on[C <: JoinCond](c: C)( 37 | implicit 38 | canJoin: CanJoin[A, From[TRepr[B, N, Rs, Ru]], C] 39 | ): FJ[TRepr[B, N, Rs, Ru], C, A] = FJ(b.repr, c, a) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/queries.scala: -------------------------------------------------------------------------------- 1 | package typed.sql 2 | 3 | import shapeless.{HList, HNil} 4 | 5 | trait Delete[S <: FSH, In] { 6 | def astData: ast.Delete 7 | def in: In 8 | } 9 | 10 | object Delete { 11 | 12 | def create[A, N, Rs <: HList, Ru <: HList](from: From[TRepr[A, N, Rs, Ru]], tableName: String): Delete[From[TRepr[A, N, Rs, Ru]], HNil] = { 13 | new Delete[From[TRepr[A, N, Rs, Ru]], HNil] { 14 | val astData: ast.Delete = ast.Delete(tableName, None) 15 | val in: HNil = HNil 16 | } 17 | } 18 | } 19 | 20 | case class Insert[In](astData: ast.InsertInto, in: In) 21 | 22 | trait Select[S <: FSH, Out, In] { 23 | type WhereFlag <: Select.HasWhere 24 | 25 | def astData: ast.Select[Out] 26 | def in: In 27 | } 28 | 29 | object Select { 30 | 31 | sealed trait HasWhere 32 | case object WhereDefined extends HasWhere 33 | case object WithoutWhere extends HasWhere 34 | 35 | def create[S <: FSH, Out](select: ast.Select[Out]): Select[S, Out, HNil] = { 36 | new Select[S, Out , HNil] { 37 | type WhereFlag = WithoutWhere.type 38 | val astData: ast.Select[Out] = select 39 | val in: HNil = HNil 40 | } 41 | } 42 | } 43 | 44 | trait Update[S <: FSH, In] { 45 | def astData: ast.Update 46 | def in: In 47 | } 48 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/syntax.scala: -------------------------------------------------------------------------------- 1 | package typed.sql 2 | 3 | import shapeless._ 4 | import shapeless.ops.adjoin.Adjoin 5 | import typed.sql.internal.{OrderByInfer, WhereInfer} 6 | import typed.sql.prefixes._ 7 | 8 | object syntax extends ColumnSyntax { 9 | 10 | object select extends ProductArgs { 11 | def applyProduct[A](query: A): SelectionPrefix[A] = SelectionPrefix(query) 12 | } 13 | 14 | object delete { 15 | 16 | def from[A, N, Rs <: HList, Ru <: HList](table: Table[A, N, Rs, Ru]): Delete[From[TRepr[A, N, Rs, Ru]], HNil] = 17 | Delete.create(From(table.repr), table.name) 18 | 19 | } 20 | 21 | def update[A, N, Rs <: HList, Ru <: HList](table: Table[A, N, Rs, Ru]): UpdationPrefix[A, N, Rs, Ru] = new UpdationPrefix(table) 22 | 23 | object insert { 24 | 25 | def into[A, N, Rs <: HList, Ru <: HList](table: Table[A, N, Rs, Ru]): InsertIntoPrefix[A, N, Rs, Ru] = new InsertIntoPrefix(table) 26 | } 27 | 28 | val `*` = All 29 | 30 | implicit class WhereSelectSyntax[S <: FSH, O](selection: Select[S, O, HNil]) { 31 | 32 | def where[C <: WhereCond, In0 <: HList](c: C)(implicit inf: WhereInfer.Aux[S, C, In0]): Select[S, O, In0] = 33 | new Select[S, O, In0] { 34 | type WhereFlag = Select.WhereDefined.type 35 | def astData: ast.Select[O] = selection.astData.copy(where = Some(inf.mkAst(c))) 36 | def in: In0 = inf.out(c) 37 | } 38 | } 39 | 40 | implicit class WhereDeleteSyntax[S <: FSH, O](deletion: Delete[S, O]) { 41 | 42 | def where[C <: WhereCond, In0 <: HList](c: C)(implicit inf: WhereInfer.Aux[S, C, In0]): Delete[S, In0] = 43 | new Delete[S, In0] { 44 | def astData: ast.Delete = deletion.astData.copy(where = Some(inf.mkAst(c))) 45 | def in: In0 = inf.out(c) 46 | } 47 | } 48 | 49 | implicit class WhereUpdateSyntax[S <: FSH, In1](upd: Update[S, In1]) { 50 | 51 | def where[C <: WhereCond, In0 <: HList, O <: HList](c: C)( 52 | implicit 53 | inf: WhereInfer.Aux[S, C, In0], 54 | adjoin: Adjoin.Aux[In1 :: In0 :: HNil, O] 55 | ): Update[S, O] = 56 | new Update[S, O] { 57 | def astData: ast.Update = upd.astData.copy(where = Some(inf.mkAst(c))) 58 | def in: O = adjoin(upd.in :: inf.out(c) :: HNil) 59 | } 60 | } 61 | 62 | implicit class OrderBySelectSyntax[S <: FSH, O, In](sel: Select[S, O, In]) { 63 | 64 | object orderBy extends ProductArgs { 65 | def applyProduct[C <: HList](c: C)(implicit inf: OrderByInfer[S, C]): Select[S, O, In] = { 66 | new Select[S, O, In] { 67 | type WhereFlag = sel.WhereFlag 68 | val astData: ast.Select[O] = { 69 | val ord = ast.OrderBy(inf.columns) 70 | sel.astData.copy(orderBy = Some(ord)) 71 | } 72 | val in: In = sel.in 73 | } 74 | } 75 | } 76 | } 77 | 78 | implicit class LimitOffsetSyntax[S <: FSH, O, In](sel: Select[S, O, In]) { 79 | 80 | def limit[J <: HList](i: Int)(implicit adjoin: Adjoin.Aux[In :: Int :: HNil, J]): Select[S, O, J] = 81 | new Select[S, O, J] { 82 | type WhereFlag = sel.WhereFlag 83 | val astData: ast.Select[O] = { 84 | sel.astData.copy(limit = Some(i)) 85 | } 86 | val in: J = adjoin(sel.in :: i :: HNil) 87 | } 88 | 89 | def offset[J <: HList](i: Int)(implicit adjoin: Adjoin.Aux[In :: Int :: HNil, J]): Select[S, O, J] = 90 | new Select[S, O, J] { 91 | type WhereFlag = sel.WhereFlag 92 | val astData: ast.Select[O] = { 93 | sel.astData.copy(offset = Some(i)) 94 | } 95 | val in: J = adjoin(sel.in :: i :: HNil) 96 | } 97 | } 98 | 99 | implicit class JoinSyntax[A <: FSH](shape: A) { 100 | 101 | def innerJoin[S2, N2, Rs2 <: HList, ru <: HList](t: Table[S2, N2, Rs2, ru]): IJPrefix[A, S2, N2, Rs2, ru] = 102 | new IJPrefix(shape, From(t.repr)) 103 | 104 | def leftJoin[S2, N2, Rs2 <: HList, ru <: HList](t: Table[S2, N2, Rs2, ru]): LJPrefix[A, S2, N2, Rs2, ru] = 105 | new LJPrefix(shape, From(t.repr)) 106 | 107 | def rightJoin[S2, N2, Rs2 <: HList, ru <: HList](t: Table[S2, N2, Rs2, ru]): RJPrefix[A, S2, N2, Rs2, ru] = 108 | new RJPrefix(shape, From(t.repr)) 109 | 110 | def fullJoin[S2, N2, Rs2 <: HList, ru <: HList](t: Table[S2, N2, Rs2, ru]): FJPrefix[A, S2, N2, Rs2, ru] = 111 | new FJPrefix(shape, From(t.repr)) 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/main/scala/typed/sql/toDoobie.scala: -------------------------------------------------------------------------------- 1 | package typed.sql 2 | 3 | import doobie.util.Read 4 | import doobie.util.fragment.Fragment 5 | import doobie.util.param.Param 6 | import doobie.util.query.Query0 7 | import doobie.util.update.Update0 8 | import shapeless.HNil 9 | import typed.sql.internal.MkWrite 10 | 11 | object toDoobie { 12 | 13 | 14 | private def renderSel(s: ast.Select[_]): String = { 15 | 16 | def renderOrderBy(orderBy: ast.OrderBy): String = { 17 | val body = orderBy.values.map({case (col, ord) => { 18 | val o = ord match { 19 | case ast.DESC => "DESC" 20 | case ast.ASC => "ASC" 21 | } 22 | s"${col.table}.${col.name} $o" 23 | }}).mkString(",") 24 | s" ORDER BY $body" 25 | } 26 | 27 | def renderWCOnd(wc: ast.WhereCond): String = wc match { 28 | case ast.WhereEq(col) => s"${col.table}.${col.name} = ?" 29 | case ast.Less(col) => s"${col.table}.${col.name} < ?" 30 | case ast.LessOrEq(col) => s"${col.table}.${col.name} <= ?" 31 | case ast.Gt(col) => s"${col.table}.${col.name} > ?" 32 | case ast.GtOrEq(col) => s"${col.table}.${col.name} >= ?" 33 | case ast.Like(col) => s"${col.table}.${col.name} like ?" 34 | case ast.In(col, size) => s"${col.table}.${col.name} in " + (0 until size).map(_ => "?").mkString("(", ",", ")") 35 | case ast.And(c1, c2) => renderWCOnd(c1) + " AND " + renderWCOnd(c2) 36 | case ast.Or(c1, c2) => renderWCOnd(c1) + " OR " + renderWCOnd(c2) 37 | } 38 | def joinCondRender(jc: ast.JoinCond): String = jc match { 39 | case ast.JoinCondEq(col1, col2) => s"${col1.table}.${col1.name} = ${col2.table}.${col2.name}" 40 | case ast.JoinCondAnd(c1, c2) => joinCondRender(c1) + " AND " + joinCondRender(c2) 41 | case ast.JoinCondOr(c1, c2) => joinCondRender(c1) + " OR " + joinCondRender(c2) 42 | } 43 | 44 | def joinRender(j: ast.Join): String = j match { 45 | case ast.InnerJoin(t, c) => s"INNER JOIN $t ON " + joinCondRender(c) 46 | case ast.LeftJoin(t, c) => s"LEFT JOIN $t ON " + joinCondRender(c) 47 | case ast.RightJoin(t, c) => s"RIGHT JOIN $t ON " + joinCondRender(c) 48 | case ast.FullJoin(t, c) => s"FULL JOIN $t ON " + joinCondRender(c) 49 | } 50 | 51 | val cols = s.cols.map(c => s"${c.table}.${c.name}").mkString(", ") 52 | val joins = s.from.joins.map(joinRender).mkString(" ") 53 | val from = s.from.table 54 | val whereR = s.where.map(c => " WHERE " + renderWCOnd(c)).getOrElse("") 55 | val order = s.orderBy.map(o => renderOrderBy(o)).getOrElse("") 56 | 57 | //TODO: move to params! 58 | val limitOpt = s.limit.map(v => s" LIMIT ?").getOrElse("") 59 | val offsetOpt = s.offset.map(v => s" OFFSET ?").getOrElse("") 60 | s"SELECT $cols FROM $from $joins" + whereR + order + limitOpt + offsetOpt 61 | } 62 | 63 | private def renderDel(d: ast.Delete): String = { 64 | def renderWCOnd(wc: ast.WhereCond): String = wc match { 65 | case ast.WhereEq(col) => s"${col.name} = ?" 66 | case ast.Less(col) => s"${col.name} < ?" 67 | case ast.LessOrEq(col) => s"${col.name} <= ?" 68 | case ast.Gt(col) => s"${col.name} > ?" 69 | case ast.GtOrEq(col) => s"${col.name} >= ?" 70 | case ast.Like(col) => s"${col.name} like ?" 71 | case ast.In(col, size) => s"${col.name} in " + (0 until size).map(_ => "?").mkString("(", ",", ")") 72 | case ast.And(c1, c2) => renderWCOnd(c1) + " AND " + renderWCOnd(c2) 73 | case ast.Or(c1, c2) => renderWCOnd(c1) + " OR " + renderWCOnd(c2) 74 | } 75 | 76 | val whereR = d.where.map(c => " WHERE " + renderWCOnd(c)).getOrElse("") 77 | s"DELETE FROM ${d.table}" + whereR 78 | } 79 | 80 | private def renderUpd(upd: ast.Update): String = { 81 | def renderWCOnd(wc: ast.WhereCond): String = wc match { 82 | case ast.WhereEq(col) => s"${col.name} = ?" 83 | case ast.Less(col) => s"${col.name} < ?" 84 | case ast.LessOrEq(col) => s"${col.name} <= ?" 85 | case ast.Gt(col) => s"${col.name} > ?" 86 | case ast.GtOrEq(col) => s"${col.name} >= ?" 87 | case ast.Like(col) => s"${col.name} like ?" 88 | case ast.In(col, size) => s"${col.name} in " + (0 until size).map(_ => "?").mkString("(", ",", ")") 89 | case ast.And(c1, c2) => renderWCOnd(c1) + " AND " + renderWCOnd(c2) 90 | case ast.Or(c1, c2) => renderWCOnd(c1) + " OR " + renderWCOnd(c2) 91 | } 92 | 93 | val whereR = upd.where.map(c => " WHERE " + renderWCOnd(c)).getOrElse("") 94 | val sets = upd.sets.map(s => s"${s.col.name} = ?").mkString(", ") 95 | s"UPDATE ${upd.table} SET $sets" + whereR 96 | } 97 | 98 | private def renderInsInto(ins: ast.InsertInto): String = { 99 | val columns = ins.columns.map(c => c.name).mkString("(", ",", ")") 100 | val values = ins.columns.map(_ => "?").mkString("(", ",", ")") 101 | s"INSERT INTO ${ins.table} $columns VALUES $values" 102 | } 103 | 104 | implicit class WrapSelection[S <: FSH, Out, In](sel: Select[S, Out, In]) { 105 | 106 | def toFragment(implicit mkWrite: MkWrite[In]): Fragment = { 107 | val sql = renderSel(sel.astData) 108 | Fragment[In](sql, sel.in, None)(mkWrite(sel.in)) 109 | } 110 | 111 | def toQuery(implicit mkWrite: MkWrite[In], read: Read[Out]): Query0[Out] = toFragment.query[Out](read) 112 | } 113 | 114 | implicit class WrapDeletion[S <: FSH, In](del: Delete[S, In]) { 115 | 116 | def toFragment(implicit mkWrite: MkWrite[In]): Fragment = { 117 | val sql = renderDel(del.astData) 118 | Fragment[In](sql, del.in, None)(mkWrite(del.in)) 119 | } 120 | 121 | def toUpdate(implicit mkWrite: MkWrite[In]): Update0 = toFragment.update 122 | } 123 | 124 | implicit class WrapUpdation[S <: FSH, In](upd: Update[S, In]) { 125 | 126 | def toFragment(implicit mkWrite: MkWrite[In]): Fragment = { 127 | val sql = renderUpd(upd.astData) 128 | Fragment[In](sql, upd.in, None)(mkWrite(upd.in)) 129 | } 130 | 131 | def toUpdate(implicit mkWrite: MkWrite[In]): Update0 = toFragment.update 132 | } 133 | 134 | implicit class WrapIns[In](ins: Insert[In]) { 135 | 136 | def toFragment(implicit mkWrite: MkWrite[In]): Fragment = { 137 | val sql = renderInsInto(ins.astData) 138 | Fragment[In](sql, ins.in, None)(mkWrite(ins.in)) 139 | } 140 | 141 | def toUpdate(implicit mkWrite: MkWrite[In]): Update0 = toFragment.update 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/test/scala/typed/sql/H2Test.scala: -------------------------------------------------------------------------------- 1 | package typed.sql 2 | 3 | import cats.data.NonEmptyList 4 | import cats.effect._ 5 | import doobie.util.transactor.Transactor 6 | import org.scalatest.FunSpec 7 | import doobie._ 8 | import doobie.implicits._ 9 | import doobie.syntax._ 10 | import cats.implicits._ 11 | import doobie.scalatest.IOChecker 12 | 13 | import typed.sql.syntax._ 14 | import typed.sql.toDoobie._ 15 | 16 | import scala.concurrent.ExecutionContext 17 | 18 | case class BRow( 19 | a: Int, 20 | b: String, 21 | c: String 22 | ) 23 | case class Row2(a2: Int) 24 | 25 | class H2Test extends FunSpec with IOChecker { 26 | 27 | implicit def contextShift: ContextShift[IO] = IO.contextShift(ExecutionContext.global) 28 | 29 | val transactor = Transactor.fromDriverManager[IO]( 30 | "org.h2.Driver", 31 | "jdbc:h2:mem:refined;DB_CLOSE_DELAY=-1", 32 | "sa", "" 33 | ) 34 | 35 | 36 | val table = Table.of[BRow].autoColumn('a).name('base) 37 | val a = table.col('a) 38 | val b = table.col('b) 39 | val c = table.col('c) 40 | 41 | val table2 = Table.of[Row2].name('test2) 42 | val a2 = table2.col('a2) 43 | 44 | val create1 = sql"CREATE TABLE base (a serial primary key, b varchar not null, c varchar not null)" 45 | create1.update.run.transact(transactor).unsafeRunSync() 46 | 47 | val create2 = sql"CREATE TABLE test2 (a2 serial primary key)" 48 | create2.update.run.transact(transactor).unsafeRunSync() 49 | 50 | describe("base") { 51 | it("insert") { check { insert.into(table).values("bv", "cv").toUpdate }} 52 | it("select *") { check { select(*).from(table).toQuery } } 53 | it("select a, b") { check { select(a, b).from(table).toQuery }} 54 | it("update b where a = 1") { check { update(table).set(b := "Upd B").where(a === 1).toUpdate }} 55 | } 56 | 57 | describe("where") { 58 | it("gt and less") { check { select(*).from(table).where(a > 1 and a < 5).toQuery }} 59 | it("geteq and lesseq") { check{ select(*).from(table).where(a >= 1 and a =< 5).toQuery }} 60 | it("eq or") { check{ select(*).from(table).where(a === 1 or a === 2).toQuery }} 61 | it("like") { check{ select(*).from(table).where(b like "BBB%").toQuery }} 62 | it("in") { check{ select(*).from(table).where(a.in(NonEmptyList.of(1,2,3))).toQuery }} 63 | } 64 | 65 | describe("order by") { 66 | 67 | it("a") { check { select(*).from(table).orderBy(a).toQuery }} 68 | it("a desc") { check { select(*).from(table).orderBy(a.DESC).toQuery }} 69 | it("a, b asc") {check { select(*).from(table).orderBy(a, b).toQuery }} 70 | } 71 | 72 | // TODO: param meta returns VARCHAR type for limit and offset 73 | // it("limit/offset") { 74 | // check{ 75 | // select(*).from(table).limit(10).offset(1).toQuery 76 | // } 77 | // } 78 | 79 | describe("joins") { 80 | 81 | it("ij") { check{ select(*).from(table.innerJoin(table2).on(a <==> a2)).toQuery }} 82 | it("lj") { check{ select(*).from(table.leftJoin(table2).on(a <==> a2)).toQuery }} 83 | it("rj") { check{ select(*).from(table.rightJoin(table2).on(a <==> a2)).toQuery }} 84 | it("fj") { check{ select(*).from(table.fullJoin(table2).on(a <==> a2)).toQuery }} 85 | it("a1+a2") { check{ select(a, a2).from(table.innerJoin(table2).on(a <==> a2)).toQuery }} 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/test/scala/typed/sql/PGTest.scala: -------------------------------------------------------------------------------- 1 | package typed.sql 2 | 3 | import cats.data.NonEmptyList 4 | import cats.effect._ 5 | import doobie.util.transactor.Transactor 6 | import org.scalatest.FunSpec 7 | import doobie._ 8 | import doobie.implicits._ 9 | import doobie.syntax._ 10 | import cats.implicits._ 11 | import doobie.scalatest.IOChecker 12 | 13 | import typed.sql.syntax._ 14 | import typed.sql.toDoobie._ 15 | 16 | import scala.concurrent.ExecutionContext 17 | 18 | //class PGTest extends FunSpec with IOChecker { 19 | // 20 | // implicit def contextShift: ContextShift[IO] = IO.contextShift(ExecutionContext.global) 21 | // 22 | // val transactor = Transactor.fromDriverManager[IO]( 23 | // "org.postgresql.Driver", "jdbc:postgresql:testdb", "tests", "12345" 24 | // ) 25 | // 26 | // 27 | // val table = Table.of[BRow].autoColumn('a).name('base) 28 | // val a = table.col('a) 29 | // val b = table.col('b) 30 | // val c = table.col('c) 31 | // 32 | // val table2 = Table.of[Row2].name('test2) 33 | // val a2 = table2.col('a2) 34 | // 35 | // val create1 = sql"CREATE TABLE IF NOT EXISTS base (a serial primary key, b varchar not null, c varchar not null)" 36 | // create1.update.run.transact(transactor).unsafeRunSync() 37 | // 38 | // val create2 = sql"CREATE TABLE IF NOT EXISTS test2 (a2 serial primary key)" 39 | // create2.update.run.transact(transactor).unsafeRunSync() 40 | // 41 | // describe("base") { 42 | // it("insert") { check { insert.into(table).values("bv", "cv").toUpdate }} 43 | // it("select *") { check { select(*).from(table).toQuery } } 44 | // it("select a, b") { check { select(a, b).from(table).toQuery }} 45 | // it("update b where a = 1") { check { update(table).set(b := "Upd B").where(a === 1).toUpdate }} 46 | // } 47 | // 48 | // describe("where") { 49 | // it("gt and less") { check { select(*).from(table).where(a > 1 and a < 5).toQuery }} 50 | // it("geteq and lesseq") { check{ select(*).from(table).where(a >= 1 and a =< 5).toQuery }} 51 | // it("eq or") { check{ select(*).from(table).where(a === 1 or a === 2).toQuery }} 52 | // it("like") { check{ select(*).from(table).where(b like "BBB%").toQuery }} 53 | // it("in") { check{ select(*).from(table).where(a.in(NonEmptyList.of(1,2,3))).toQuery }} 54 | // } 55 | // 56 | // describe("order by") { 57 | // 58 | // it("a") { check { select(*).from(table).orderBy(a).toQuery }} 59 | // it("a desc") { check { select(*).from(table).orderBy(a.DESC).toQuery }} 60 | // it("a, b asc") {check { select(*).from(table).orderBy(a, b).toQuery }} 61 | // } 62 | // 63 | //// TODO: param meta returns BIGINT type for limit and offset 64 | //// it("limit/offset") { 65 | //// check{ 66 | //// select(*).from(table).limit(10).offset(1).toQuery 67 | //// } 68 | //// } 69 | // 70 | // describe("joins") { 71 | // 72 | // it("ij") { check{ select(*).from(table.innerJoin(table2).on(a <==> a2)).toQuery }} 73 | // it("lj") { check{ select(*).from(table.leftJoin(table2).on(a <==> a2)).toQuery }} 74 | // it("rj") { check{ select(*).from(table.rightJoin(table2).on(a <==> a2)).toQuery }} 75 | // it("fj") { check{ select(*).from(table.fullJoin(table2).on(a <==> a2)).toQuery }} 76 | // it("a1+a2") { check{ select(a, a2).from(table.innerJoin(table2).on(a <==> a2)).toQuery }} 77 | // } 78 | //} 79 | -------------------------------------------------------------------------------- /src/test/scala/typed/sql/TestSyntax.scala: -------------------------------------------------------------------------------- 1 | package typed.sql 2 | 3 | import cats.data.NonEmptyList 4 | import org.scalatest.{FunSpec, Matchers} 5 | import shapeless._ 6 | import shapeless.test.illTyped 7 | import typed.sql.syntax._ 8 | 9 | class TestSyntax extends FunSpec with Matchers{ 10 | 11 | case class TestRow( 12 | a: Int, 13 | b: String, 14 | c: Boolean 15 | ) 16 | val table1 = Table.of[TestRow].autoColumn('a).name('my_table) 17 | val a1 = table1.col('a) 18 | val b1 = table1.col('b) 19 | 20 | describe("delete") { 21 | 22 | it("delete") { 23 | delete.from(table1).astData shouldBe ast.Delete("my_table", None) 24 | } 25 | 26 | it("delete where") { 27 | val exp = ast.Delete("my_table", Some(ast.WhereEq(ast.Col("my_table", "a")))) 28 | delete.from(table1).where(a1 === 2).astData shouldBe exp 29 | } 30 | } 31 | 32 | describe("update") { 33 | 34 | it("update column") { 35 | val x = update(table1).set(b1 := "yoyo") 36 | x.astData shouldBe ast.Update("my_table", List(ast.Set(ast.Col("my_table", "b"))), None) 37 | } 38 | 39 | it("can't update primary key") { 40 | illTyped{"update(table1).set(a1 := 42)"} 41 | } 42 | 43 | it("with where") { 44 | val x = update(table1).set(b1 := "yoyo").where(a1 === 4) 45 | x.astData shouldBe ast.Update("my_table", List(ast.Set(ast.Col("my_table", "b"))), Some(ast.WhereEq(ast.Col("my_table", "a")))) 46 | } 47 | } 48 | 49 | describe("insert into") { 50 | 51 | it("insert all") { 52 | val x = insert.into(table1).values("b_value", "c_value") 53 | val data = x.astData 54 | 55 | val exp = ast.InsertInto("my_table", List(ast.Col("my_table", "b"), ast.Col("my_table", "c"))) 56 | data shouldBe exp 57 | 58 | x.in shouldBe ("b_value" :: "c_value" :: HNil) 59 | } 60 | } 61 | 62 | describe("where") { 63 | 64 | it("eq") { 65 | val x = select(a1).from(table1).where(a1 === 2) 66 | x.astData.where.get shouldBe ast.WhereEq(ast.Col("my_table", "a")) 67 | } 68 | 69 | it("gt") { 70 | val x = select(a1).from(table1).where(a1 > 2) 71 | x.astData.where.get shouldBe ast.Gt(ast.Col("my_table", "a")) 72 | } 73 | 74 | it("gt or eq") { 75 | val x = select(a1).from(table1).where(a1 >= 2) 76 | x.astData.where.get shouldBe ast.GtOrEq(ast.Col("my_table", "a")) 77 | } 78 | 79 | it("less") { 80 | val x = select(a1).from(table1).where(a1 < 2) 81 | x.astData.where.get shouldBe ast.Less(ast.Col("my_table", "a")) 82 | } 83 | 84 | it("less or eq") { 85 | val x = select(a1).from(table1).where(a1 =< 2) 86 | x.astData.where.get shouldBe ast.LessOrEq(ast.Col("my_table", "a")) 87 | } 88 | 89 | it("like") { 90 | val x = select(a1).from(table1).where(b1 like "BBB%") 91 | x.astData.where.get shouldBe ast.Like(ast.Col("my_table", "b")) 92 | } 93 | 94 | it("and") { 95 | val x = select(a1).from(table1).where(a1 === 2 and a1 === 3) 96 | x.astData.where.get shouldBe ast.And(ast.WhereEq(ast.Col("my_table", "a")), ast.WhereEq(ast.Col("my_table", "a"))) 97 | } 98 | 99 | it("or") { 100 | val x = select(a1).from(table1).where(a1 === 2 or a1 === 3) 101 | x.astData.where.get shouldBe ast.Or(ast.WhereEq(ast.Col("my_table", "a")), ast.WhereEq(ast.Col("my_table", "a"))) 102 | } 103 | 104 | it("and and") { 105 | val x = select(a1).from(table1).where(a1 === 2 and a1 === 3 and a1 === 4) 106 | x.astData.where.get shouldBe 107 | ast.And( 108 | ast.And( 109 | ast.WhereEq(ast.Col("my_table", "a")), 110 | ast.WhereEq(ast.Col("my_table", "a")) 111 | ), 112 | ast.WhereEq(ast.Col("my_table", "a")) 113 | ) 114 | } 115 | 116 | it("in") { 117 | val x = select(a1).from(table1).where(a1 in NonEmptyList.of(1, 2, 3)) 118 | x.astData.where.get shouldBe ast.In(ast.Col("my_table", "a"), 3) 119 | } 120 | } 121 | 122 | describe("select") { 123 | 124 | 125 | describe("order by ") { 126 | it("order by default") { 127 | val x = select(a1).from(table1).orderBy(b1) 128 | val data = x.astData 129 | 130 | val exp = ast.Select( 131 | List(ast.Col("my_table", "a")), 132 | ast.From("my_table", List.empty), 133 | None, 134 | Some(ast.OrderBy(List(ast.Col("my_table", "b") -> ast.ASC))), 135 | None, None 136 | ) 137 | data shouldBe exp 138 | } 139 | 140 | it("order by asc") { 141 | val x = select(a1).from(table1).orderBy(b1.ASC) 142 | val data = x.astData 143 | 144 | val exp = ast.Select( 145 | List(ast.Col("my_table", "a")), 146 | ast.From("my_table", List.empty), 147 | None, 148 | Some(ast.OrderBy(List(ast.Col("my_table", "b") -> ast.ASC))), 149 | None, None 150 | ) 151 | data shouldBe exp 152 | } 153 | 154 | it("order by desc") { 155 | val x = select(a1).from(table1).orderBy(b1.DESC) 156 | val data = x.astData 157 | 158 | val exp = ast.Select( 159 | List(ast.Col("my_table", "a")), 160 | ast.From("my_table", List.empty), 161 | None, 162 | Some(ast.OrderBy(List(ast.Col("my_table", "b") -> ast.DESC))), 163 | None, None 164 | ) 165 | data shouldBe exp 166 | } 167 | 168 | it("order by mult") { 169 | val x = select(a1).from(table1).orderBy(a1, b1) 170 | val data = x.astData 171 | 172 | val exp = ast.Select( 173 | List(ast.Col("my_table", "a")), 174 | ast.From("my_table", List.empty), 175 | None, 176 | Some(ast.OrderBy( 177 | List( 178 | ast.Col("my_table", "a") -> ast.ASC, 179 | ast.Col("my_table", "b") -> ast.ASC 180 | ) 181 | )), 182 | None, None 183 | ) 184 | data shouldBe exp 185 | } 186 | } 187 | } 188 | } 189 | --------------------------------------------------------------------------------