├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── build.sbt ├── build └── publish_develop.sh ├── changelog.md ├── docs ├── MySQL.md ├── Postgres.md ├── design.md └── structure.graffle ├── morpheus-dsl └── src │ ├── main │ └── scala │ │ └── com │ │ └── outworkers │ │ └── morpheus │ │ ├── Row.scala │ │ ├── SQLDataTypes.scala │ │ ├── builder │ │ ├── QueryBuilder.scala │ │ └── SQLBuiltQuery.scala │ │ ├── column │ │ ├── AbstractColumn.scala │ │ ├── ForeignKey.scala │ │ ├── Index.scala │ │ ├── NumericColumns.scala │ │ ├── PrimitiveColumn.scala │ │ └── StringColumns.scala │ │ ├── dsl │ │ ├── BaseTable.scala │ │ ├── DefaultImportsDefinition.scala │ │ ├── ResultSetOperations.scala │ │ └── SelectTable.scala │ │ ├── engine │ │ └── query │ │ │ ├── AbstractQueryColumn.scala │ │ │ ├── CreateQuery.scala │ │ │ ├── DeleteQuery.scala │ │ │ ├── InsertQuery.scala │ │ │ ├── Query.scala │ │ │ ├── RootSelectQuery.scala │ │ │ ├── SQLEngine.scala │ │ │ ├── SQLQuery.scala │ │ │ ├── UpdateQuery.scala │ │ │ ├── package.scala │ │ │ └── parts │ │ │ ├── Part.scala │ │ │ └── QueryPart.scala │ │ ├── keys │ │ └── Key.scala │ │ ├── operators │ │ └── Operator.scala │ │ └── sql │ │ ├── Columns.scala │ │ ├── DefaultSQLImplicits.scala │ │ ├── SQLDatabase.scala │ │ ├── SQLTable.scala │ │ └── package.scala │ └── test │ └── scala │ └── com │ └── outworkers │ └── morpheus │ ├── CustomSamplers.scala │ ├── builder │ └── SQLQueryTest.scala │ ├── column │ └── AbstractColumnTest.scala │ ├── dsl │ ├── BasicTable.scala │ ├── SQLBuiltQueryTest.scala │ └── TableTest.scala │ ├── engine │ └── query │ │ ├── CompileTimeRestrictionsTest.scala │ │ ├── CreateQueryTest.scala │ │ ├── DeleteQuerySerialisationTest.scala │ │ ├── InFlightOperatorsTest.scala │ │ ├── InsertQuerySerialisationTest.scala │ │ ├── JoinsQuerySerialisationTest.scala │ │ ├── SQLPrimitivesTest.scala │ │ ├── SelectQuerySerialisationTest.scala │ │ ├── UpdateQuerySerialisationTest.scala │ │ └── WhereClauseOperatorsTest.scala │ ├── helpers │ └── TestRow.scala │ ├── schema │ ├── KeysSerialisationTest.scala │ ├── NumericColumnsSerialisationTest.scala │ ├── StringColumnSerialisationTest.scala │ └── ZeroFillColumnsSerialisationTest.scala │ └── tables │ └── IndexTable.scala ├── morpheus-mysql └── src │ ├── main │ └── scala │ │ └── com │ │ └── outworkers │ │ └── morpheus │ │ └── mysql │ │ ├── Columns.scala │ │ ├── DataTypes.scala │ │ ├── Implicits.scala │ │ ├── QueryBuilder.scala │ │ ├── Table.scala │ │ ├── dsl │ │ └── package.scala │ │ └── query │ │ ├── DeleteQuery.scala │ │ ├── RootCreateQuery.scala │ │ ├── RootInsertQuery.scala │ │ ├── SelectQuery.scala │ │ └── UpdateQuery.scala │ └── test │ └── scala │ └── com │ └── outworkers │ └── morpheus │ └── mysql │ ├── DatatypesTest.scala │ ├── EmptyRow.scala │ ├── db │ ├── Connector.scala │ ├── CreateQueryTest.scala │ ├── InsertQueryDBTest.scala │ ├── SelectQueryTest.scala │ ├── UpdateQueryTest.scala │ └── specialized │ │ └── EnumerationColumnTest.scala │ ├── query │ ├── DeleteQueryTest.scala │ ├── InsertQueryTest.scala │ ├── QueryBuilderTest.scala │ ├── UpdateQueryTest.scala │ └── parts │ │ └── PartsTest.scala │ └── tables │ ├── PrimitivesTable.scala │ └── Tables.scala ├── morpheus.graffle ├── project ├── Publishing.scala ├── build.properties └── plugins.sbt ├── scalastyle-config.xml └── sonar-project.properties /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | atlassian-ide-plugin.xml 5 | 6 | cassandra.jar 7 | 8 | # Eclipse specific 9 | .classpath 10 | .project 11 | .settings/ 12 | .metadata 13 | 14 | # Project statistics 15 | stats 16 | 17 | # InteliJ IDEA 18 | *.iml 19 | 20 | # sbt specific 21 | dist/* 22 | target/ 23 | lib_managed/ 24 | src_managed/ 25 | project/boot/ 26 | project/plugins/project/ 27 | 28 | # Scala-IDE specific 29 | .scala_dependencies 30 | 31 | # MacOS X specific 32 | .DS_Store 33 | 34 | #InteliJ 35 | .idea_modules/ 36 | .idea/ 37 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: scala 2 | services: 3 | - mysql 4 | cache: 5 | directories: 6 | - $HOME/.ivy2/cache 7 | - $HOME/.sbt/boot/scala-$TRAVIS_SCALA_VERSION 8 | sudo: false 9 | 10 | scala: 11 | - 2.10.6 12 | - 2.11.12 13 | 14 | env: 15 | global: 16 | - GH_REF: github.com/outworkers/morpheus.git 17 | - secure: RtMttctU8NUIcVO9y+ygDXKVtnwE8TZ/UrvjUD64c1FAoWotsviHawCaZ1/3Tga0DY3JOpVA/zafkmzlJhtP6yyqBkZKsujeDw0Xt6WJ5Y4bxHW2ucBjRSz1EcCIzSKfuW1fXlvndErjN4pTHmKQzi/ahtFlKn1NWtGjPR5WyrgD+mfCcxAdnlDasJ3FcmklZMFwupDuJK/9lJFXI5dcoG8aX7U5u2dxEIVpLYRLX9cpaDTOxC8rBZRtNVqjJh9A3unfQ13XAcrFWcdEuDXHQZ9rdFpXqskXOCsaFDT/dZUPyjy/rdN6GURM00p6FFy//w6qBLW9FWWtGsESiabTvxSLPP/uG+LB6E2oxl1mnQ0rfTxXmCVW1wewNUlICFYKGqSYaTgx8TQUilAfPHxzaJgWDPKeFGEPu9Cgl4sznaEA5soVX2EXy2iIrRi7PFOVyyYSHDDWLuoodApzYjwIznEUUi121zv8LvosdMtbEYG5rtaNHhVpzbiGKwDiowJQBlsZVf8DSQFMljL9J6tfd7awm1cl5QGbFJAmM9NTkvv9eU2UpoOWNWmmTwnX225RseFFvMWC74Dz0sFfGdbo/K+8EJ5k398f5Spn1BxxuXVuIy7Aii8NuxQ99U1ZKVEcKNd+vkKvLMQ0fI6/WhpHaHJut5x1eWyHSckCCbMVMFo= 18 | - secure: e35fJ2QDCpsKtXqgAMU9pon2LS7bvQS2wRDjE8vpFwe7dLSMXnNhOaE58HQPfqbFD/QS6nwAyYip7iBQ55tjntBTBQ1RiePgjkoC9HK1VR7qvw22Enx5bux5b/UIwJcy41BIKmH5/mE8z05FdJLha3HO2tPVmaeXP+asALP70xR0js1smCGWGyowhUsqQL6fgXdw4f80vIrogQXuyR1KzVvr8U/iIlmb7Ys5jxqQ76btBLQYTw6pHNLVebMkgQIh+ZTR9tKnnFZdj0VNFGhWAlxeps8srsLXf0dxSUckoAYFNLjz37fvOQ89LrY4PQH/9mcQQvW01GMS8lJSCG5eL/ZhoefeVhkvwOZHfzIv53VxIXoqNIhuAM2Cg1WZgd0SZ0e/F1OEM6Dv3jSGlsCa6U+/G4HGGIJ5NVSWJQXgeH1fs56RbIKhpSdd6bIAlZ6JP0Zq7p+Ty06UKUQ+3Fe9MPZVmNw9iaB1A3Lw7XpFYsCIcmyoWtpV+3VpWI/LduOdpaHOv4PJCbx+dD4728mbutYwJkHpLB5LqLCI3YuKAnqWGWT0iBqJFGSNlIyYN30K/MpcE8l79vnbAZ6Y7bGWttnuEfz1e3ewCdXhDuxq6BQdgL3wmmRyS8HTJhFgjCymoCn5kbhiaw1qiKdXnTp4/uokEqNzeb2QgxS02T3tZ6Q= 19 | - secure: W1HqSPhT74PxImeoIqy7y8JoAlmgRN0o4cFU9Uy9+3HA7IWDUhSqUKqimlOjtIrUirux9lS6a0TvJcv8culbnAlD00LKcOm+qWRVoUdOyHFc70t3CKOmiFx6taYJBYbSeKyLf00HNDMXv8jxRywgoQcadxM7lWhDNnvxw3HK4x9odaWbg6TKruE2PF1VM2LoS2iSXZ98wbTqhMWknvJSItH6jJjq4yuRguLl45bwAU3nyycFPCvssi9Qbf3g7jyWAv5/yHhdzVZyfWUeA0Cq+SPp8d6qAeQ1ooVMJ+usTKOoo47tieSkjvx531DBTdko6pk0PyhGUAVE484Xo7J6r/sIE9uIE6G4I6osM6rrqkWyBbShUI+BJC9rr305aytnY2B7EpYJq6AvVmrLD+OQn45FPllgFomBg4R2wPVY+VcZ77ugiPLuGn0A/d5Osp6u4gtpclne81ZQ7M9e4c4BBlKO2BEuJhM3YoTfGq45WZggDmUMhJukFW7BENePdfWwV5uGOgFFIY8Aeh2mIIifQGJKLNrefPYwJuUqca42lNbIxHaV3QBpIlEBlt1xUL3uDk8HxkRdkyDnMPK5k9ffCmKIDf/klqkARsYSrTlVATfqJXpsmDGZnqh/tdnruqkR26y6M/d5XssvXx4jPkRvmEX5AcOWpjXmKY+oHBeszhU= 20 | notifications: 21 | slack: 22 | - websudos:P9QNXx1ZGFnDHp3v3jUqtB8k 23 | email: 24 | - dev@websudos.co.uk 25 | branches: 26 | only: 27 | - master 28 | - develop 29 | 30 | jdk: 31 | - oraclejdk8 32 | 33 | before_script: 34 | - travis_retry sbt ++$TRAVIS_SCALA_VERSION update 35 | - mysql -e "CREATE DATABASE morpheus_test;" 36 | - mysql -e "CREATE USER 'morpheus'@'localhost' IDENTIFIED BY 'morpheus23!';" 37 | - mysql -e "SET PASSWORD FOR 'travis'@'localhost' = PASSWORD('morpheus23!')" 38 | script: 39 | - sbt ++$TRAVIS_SCALA_VERSION clean coverage test coverageReport coverageAggregate 40 | coveralls 41 | after_success: 42 | - ./build/publish_develop.sh 43 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing to morpheus 2 | ======================= 3 | back to top 4 | 5 | Contributions are most welcome! Don't forget to add your name and GitHub handle to the list of contributors. 6 | 7 | Using GitFlow 8 | ================================== 9 | back to top 10 | 11 | To contribute, simply submit a "Pull request" via GitHub. 12 | 13 | We use GitFlow as a branching model and SemVer for versioning. 14 | 15 | - When you submit a "Pull request" we require all changes to be squashed. 16 | - We never merge more than one commit at a time. All the n commits on your feature branch must be squashed. 17 | - We won't look at the pull request until Travis CI says the tests pass, make sure tests go well. 18 | 19 | Scala Style Guidelines 20 | =================================================== 21 | back to top 22 | 23 | In spirit, we follow the [Twitter Scala Style Guidelines](http://twitter.github.io/effectivescala/). 24 | We will reject your pull request if it doesn't meet code standards, but we'll happily give you a hand to get it right. Morpheus is even using ScalaStyle to 25 | build, which means your build will also fail if your code doesn't comply with the style rules. 26 | 27 | Some of the things that will make us seriously frown: 28 | 29 | - Blocking when you don't have to. It just makes our eyes hurt when we see useless blocking. 30 | - Testing should be thread safe and fully async, use ```ParallelTestExecution``` if you want to show off. 31 | - Writing tests should use the pre-existing tools. 32 | - Use the common patterns you already see here, we've done a lot of work to make it easy. 33 | - Don't randomly import stuff. We are very big on alphabetized clean imports. 34 | - Morpheus uses ScalaStyle during Travis CI runs to guarantee you are complying with our guidelines. Since breaking the rules will result in a failed build, 35 | please take the time to read through the guidelines beforehand. 36 | 37 | 38 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2019 Outworkers, Limited. 3 | * 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * - Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * 12 | * - Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | import Publishing.{ciSkipSequence, releaseTutFolder} 29 | import sbtrelease.ReleaseStateTransformations._ 30 | 31 | lazy val Versions = new { 32 | val util = "0.50.0" 33 | val spark = "1.2.1" 34 | val FinaglePostgres = "0.1.0" 35 | val shapeless = "2.3.2" 36 | val diesel = "0.5.0" 37 | val lift = "3.0" 38 | val slf4j = "1.7.21" 39 | val joda = "2.9.4" 40 | val scalatest = "3.0.5" 41 | val jodaConvert = "1.8.1" 42 | val scalacheck = "1.14.0" 43 | 44 | val finagle: String => String = { s => 45 | CrossVersion.partialVersion(s) match { 46 | case Some((_, minor)) if minor >= 12 => "6.42.0" 47 | case _ => "6.35.0" 48 | } 49 | } 50 | 51 | val twitterUtil: String => String = { 52 | s => CrossVersion.partialVersion(s) match { 53 | case Some((_, minor)) if minor >= 12 => "6.41.0" 54 | case _ => "6.34.0" 55 | } 56 | } 57 | } 58 | 59 | val liftVersion: String => String = { 60 | s => CrossVersion.partialVersion(s) match { 61 | case Some((_, minor)) if minor >= 11 => Versions.lift 62 | case _ => "3.0-M1" 63 | } 64 | } 65 | 66 | lazy val releaseSettings = Seq( 67 | releaseTutFolder := baseDirectory.value / "docs", 68 | releaseIgnoreUntrackedFiles := true, 69 | releaseVersionBump := sbtrelease.Version.Bump.Minor, 70 | releaseTagComment := s"Releasing ${(version in ThisBuild).value} $ciSkipSequence", 71 | releaseCommitMessage := s"Setting version to ${(version in ThisBuild).value} $ciSkipSequence", 72 | releaseProcess := Seq[ReleaseStep]( 73 | checkSnapshotDependencies, 74 | inquireVersions, 75 | setReleaseVersion, 76 | Publishing.commitTutFilesAndVersion, 77 | releaseStepCommandAndRemaining("such publishSigned"), 78 | releaseStepCommandAndRemaining("sonatypeReleaseAll"), 79 | tagRelease, 80 | setNextVersion, 81 | commitNextVersion, 82 | pushChanges 83 | ) 84 | ) 85 | 86 | lazy val sharedSettings: Seq[Def.Setting[_]] = Seq( 87 | organization := "com.outworkers", 88 | scalaVersion := "2.11.12", 89 | crossScalaVersions := Seq("2.10.6", "2.11.12"), 90 | resolvers ++= Seq( 91 | Resolver.typesafeRepo("releases"), 92 | Resolver.sonatypeRepo("releases"), 93 | Resolver.jcenterRepo, 94 | "Twitter Repository" at "http://maven.twttr.com" 95 | ), 96 | scalacOptions ++= Seq( 97 | "-language:postfixOps", 98 | "-language:implicitConversions", 99 | "-language:reflectiveCalls", 100 | "-language:higherKinds", 101 | "-language:existentials", 102 | "-Yinline-warnings", 103 | "-Xlint", 104 | "-deprecation", 105 | "-feature", 106 | "-unchecked" 107 | ), 108 | fork in Test := true, 109 | javaOptions in Test ++= Seq("-Xmx2G") 110 | ) ++ Publishing.effectiveSettings ++ releaseSettings 111 | 112 | lazy val morpheus = (project in file(".")) 113 | .settings(sharedSettings: _*) 114 | .settings( 115 | name := "morpheus", 116 | moduleName := "morpheus" 117 | ).aggregate( 118 | morpheusDsl, 119 | morpheusMySQL 120 | ) 121 | 122 | lazy val morpheusDsl = (project in file("morpheus-dsl")) 123 | .settings(sharedSettings: _*) 124 | .settings( 125 | name := "morpheus-dsl", 126 | moduleName := "morpheus-dsl", 127 | libraryDependencies ++= Seq( 128 | "org.scala-lang" % "scala-compiler" % scalaVersion.value % "provided", 129 | "com.twitter" %% "util-core" % Versions.twitterUtil(scalaVersion.value), 130 | "org.slf4j" % "slf4j-api" % Versions.slf4j, 131 | "com.chuusai" %% "shapeless" % Versions.shapeless, 132 | "joda-time" % "joda-time" % Versions.joda, 133 | "org.joda" % "joda-convert" % Versions.jodaConvert, 134 | "net.liftweb" %% "lift-json" % liftVersion(scalaVersion.value) % Test, 135 | "com.outworkers" %% "util-samplers" % Versions.util % Test, 136 | "org.scalatest" %% "scalatest" % Versions.scalatest % Test, 137 | "org.scalacheck" %% "scalacheck" % Versions.scalacheck % Test 138 | ) 139 | ) 140 | 141 | lazy val morpheusMySQL = (project in file("morpheus-mysql")) 142 | .settings(sharedSettings: _*) 143 | .settings( 144 | moduleName := "morpheus-mysql", 145 | name := "morpheus-mysql", 146 | libraryDependencies ++= Seq( 147 | "com.twitter" %% "finagle-mysql" % Versions.finagle(scalaVersion.value), 148 | "com.outworkers" %% "util-samplers" % Versions.util % Test, 149 | "org.scalatest" %% "scalatest" % Versions.scalatest % Test, 150 | "org.scalacheck" %% "scalacheck" % Versions.scalacheck % Test 151 | ) 152 | ).dependsOn( 153 | morpheusDsl % "compile->compile;test->test;" 154 | ) 155 | -------------------------------------------------------------------------------- /build/publish_develop.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | if [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_BRANCH" == "develop" ]; 3 | then 4 | 5 | if [ "${TRAVIS_SCALA_VERSION}" == "2.11.8" ] && [ "${TRAVIS_JDK_VERSION}" == "oraclejdk8" ]; 6 | then 7 | 8 | echo "Setting git user email to ci@outworkers.com" 9 | git config user.email "ci@outworkers.com" 10 | 11 | echo "Setting git user name to Travis CI" 12 | git config user.name "Travis CI" 13 | 14 | echo "The current JDK version is ${TRAVIS_JDK_VERSION}" 15 | echo "The current Scala version is ${TRAVIS_SCALA_VERSION}" 16 | 17 | echo "Creating credentials file" 18 | if [ -e "$HOME/.bintray/.credentials" ]; then 19 | echo "Bintray redentials file already exists" 20 | else 21 | mkdir -p "$HOME/.bintray/" 22 | touch "$HOME/.bintray/.credentials" 23 | echo "realm = Bintray API Realm" >> "$HOME/.bintray/.credentials" 24 | echo "host = api.bintray.com" >> "$HOME/.bintray/.credentials" 25 | echo "user = $bintray_user" >> "$HOME/.bintray/.credentials" 26 | echo "password = $bintray_password" >> "$HOME/.bintray/.credentials" 27 | fi 28 | 29 | if [ -e "$HOME/.bintray/.credentials" ]; then 30 | echo "Bintray credentials file successfully created" 31 | else 32 | echo "Bintray credentials still not found" 33 | fi 34 | 35 | sbt version-bump-patch git-tag 36 | 37 | echo "Pushing tag to GitHub." 38 | 39 | git push --tags "https://${github_token}@${GH_REF}" 40 | 41 | echo "Publishing Bintray artifact" 42 | 43 | "Publishing a new version to Bintray" 44 | sbt +publish 45 | 46 | git add . 47 | 48 | git commit -m "Automatically incrementing tag version." 49 | 50 | echo "Printing available remotes" 51 | git remote -v 52 | 53 | git push "https://${github_token}@${GH_REF}" develop 54 | 55 | git push "https://${github_token}@${GH_REF}" develop:master 56 | 57 | else 58 | echo "Only publishing version for Scala 2.11.8 and Oracle JDK 8 to prevent multiple artifacts" 59 | fi 60 | else 61 | echo "This is either a pull request or the branch is not develop, deployment not necessary" 62 | fi 63 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | Version history 5 | =========================================== 6 | 7 |
68 | * {{{class MyTable extends Table[MyTable, MyRecord { 69 | * override val tableName = "custom" 70 | * }}} 71 | *72 | * 73 | * The default name in the above case would have been "MyTable". 74 | */ 75 | private[this] lazy val _name: String = { 76 | cm.reflect(this).symbol.name.toTypeName.decodedName.toString 77 | } 78 | 79 | /** 80 | * The most notable and honorable of functions in this file, this is what allows our DSL to provide type-safety. 81 | * It works by requiring a user to define a type-safe mapping between a buffered Result and the above refined Record. 82 | * 83 | * Objects delimiting pre-defined columns also have a pre-defined "apply" method, allowing the user to simply autofill the type-safe mapping by using 84 | * pre-existing definitions. 85 | * 86 | * @param row The row incoming as a result from a MySQL query. 87 | * @return A Record instance. 88 | */ 89 | def fromRow(row: TableRow): Record 90 | 91 | def tableName: String = _name 92 | 93 | def create: RootCreateQuery[Owner, Record, TableRow] 94 | 95 | def update: RootUpdateQuery[Owner, Record, TableRow] 96 | 97 | def delete: RootDeleteQuery[Owner, Record, TableRow] 98 | 99 | def insert: RootInsertQuery[Owner, Record, TableRow] 100 | 101 | def columns: List[AbstractColumn[_]] = _columns.toList 102 | 103 | Lock.synchronized { 104 | val instanceMirror = cm.reflect(this) 105 | val selfType = instanceMirror.symbol.toType 106 | 107 | // Collect all column definitions starting from base class 108 | val columnMembers = MutableArrayBuffer.empty[Symbol] 109 | selfType.baseClasses.reverse.foreach { 110 | baseClass => 111 | val baseClassMembers = baseClass.typeSignature.members.sorted 112 | val baseClassColumns = baseClassMembers.filter(_.typeSignature <:< ru.typeOf[AbstractColumn[_]]) 113 | baseClassColumns.foreach(symbol => if (!columnMembers.contains(symbol)) columnMembers += symbol) 114 | } 115 | 116 | columnMembers.foreach { 117 | symbol => 118 | val column = instanceMirror.reflectModule(symbol.asModule).instance 119 | _columns += column.asInstanceOf[AbstractColumn[_]] 120 | } 121 | } 122 | } 123 | 124 | 125 | 126 | private[morpheus] case object Lock 127 | -------------------------------------------------------------------------------- /morpheus-dsl/src/main/scala/com/outworkers/morpheus/dsl/DefaultImportsDefinition.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.dsl 17 | 18 | import com.outworkers.morpheus 19 | import com.outworkers.morpheus.Row 20 | import com.outworkers.morpheus.column.{AbstractColumn, DefaultForeignKeyConstraints} 21 | import com.outworkers.morpheus.engine.query.AbstractQueryColumn 22 | 23 | import scala.util.Try 24 | 25 | /** 26 | * As the implementation of SQL builders may differ depending on the type of SQL database in use, we will provide a series of specific imports for each 27 | * individual database. 28 | * 29 | * For instance, for MySQL a user will {{ import com.outworkers.morpheus.mysql.dsl._ }}, for Postgress the user will {{import com.outworkers.morpheus 30 | * .postgres._ }} and so on. To make our life easy when we reach the point of writing disjoint import objects for the various SQL databases, 31 | * this trait will provide the base implementation of an "all you can eat" imports object. 32 | * 33 | * This includes the various conversions and implicit mechanisms which will have there own underlying structure. As any underlying variable implementation 34 | * between databases will still extend the same base implementations internally, we can easily override with the settings we need in any place we want. 35 | * 36 | * Thanks to Scala's ability to override the below type aliases, we can easily swap out the base BaseTable implementation for a MySQL specific BaseTable 37 | * implementation in a manner that's completely invisible to the API user. The naming of things can stay the same while morpheus invisibly implements all 38 | * necessary discrepancies. 39 | */ 40 | trait DefaultImportsDefinition extends DefaultForeignKeyConstraints { 41 | 42 | type DataType[T] = morpheus.DataType[T] 43 | 44 | object SQLPrimitive { 45 | def apply[T <: Enumeration](enum: T)(implicit ev: DataType[String]): DataType[T#Value] = { 46 | new DataType[T#Value] { 47 | 48 | override def sqlType: String = ev.sqlType 49 | 50 | override def deserialize(row: com.outworkers.morpheus.Row, name: String): Try[T#Value] = { 51 | row.string(name) map { s => enum.withName(s) } 52 | } 53 | 54 | override def serialize(value: T#Value): String = ev.serialize(value.toString) 55 | } 56 | } 57 | } 58 | 59 | type BaseTable[ 60 | Owner <: BaseTable[Owner, Record, TableRow], 61 | Record, 62 | TableRow <: Row 63 | ] = com.outworkers.morpheus.dsl.BaseTable[Owner, Record, TableRow] 64 | 65 | type PrimaryKey = com.outworkers.morpheus.keys.PrimaryKey 66 | type UniqueKey = com.outworkers.morpheus.keys.UniqueKey 67 | type NotNull = com.outworkers.morpheus.keys.NotNull 68 | type Autoincrement = com.outworkers.morpheus.keys.Autoincrement 69 | type Zerofill = com.outworkers.morpheus.keys.Zerofill 70 | 71 | implicit def columnToQueryColumn[T : DataType](col: AbstractColumn[T]): AbstractQueryColumn[T] 72 | 73 | } 74 | 75 | -------------------------------------------------------------------------------- /morpheus-dsl/src/main/scala/com/outworkers/morpheus/dsl/ResultSetOperations.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.dsl 17 | 18 | import scala.concurrent.{Future => ScalaFuture, Promise => ScalaPromise} 19 | 20 | import com.twitter.util.{ Future, Throw, Return } 21 | import com.outworkers.morpheus.{Row, Result, Client} 22 | 23 | private[morpheus] trait ResultSetOperations { 24 | 25 | protected[this] def queryToFuture[DBRow <: Row, DBResult <: Result](query: String)(implicit client: Client[DBRow, DBResult]): Future[DBResult] = { 26 | client.query(query) 27 | } 28 | 29 | protected[this] def queryToScalaFuture[DBRow <: Row, DBResult <: Result](query: String)(implicit client: Client[DBRow, DBResult]): ScalaFuture[DBResult] = { 30 | twitterToScala(client.query(query)) 31 | } 32 | 33 | protected[this] def twitterToScala[A](future: Future[A]): ScalaFuture[A] = { 34 | val promise = ScalaPromise[A]() 35 | future respond { 36 | case Return(data) => promise success data 37 | case Throw(err) => promise failure err 38 | } 39 | promise.future 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /morpheus-dsl/src/main/scala/com/outworkers/morpheus/engine/query/CreateQuery.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.engine.query 17 | 18 | import com.outworkers.morpheus.Row 19 | import com.outworkers.morpheus.sql.DefaultRow 20 | import com.outworkers.morpheus.builder.{AbstractSQLSyntax, AbstractSyntaxBlock, DefaultSQLSyntax, SQLBuiltQuery} 21 | import com.outworkers.morpheus.dsl.BaseTable 22 | import shapeless.{HList, HNil} 23 | 24 | private[morpheus] class RootCreateSyntaxBlock(query: String, tableName: String) extends AbstractSyntaxBlock { 25 | 26 | protected[this] val qb: SQLBuiltQuery = SQLBuiltQuery(query) 27 | 28 | def syntax: AbstractSQLSyntax = DefaultSQLSyntax 29 | 30 | def default: SQLBuiltQuery = { 31 | qb.pad.append(DefaultSQLSyntax.table) 32 | .forcePad.appendEscape(tableName) 33 | } 34 | 35 | def ifNotExists: SQLBuiltQuery = { 36 | qb.pad.append(DefaultSQLSyntax.table) 37 | .forcePad.append(syntax.ifNotExists) 38 | .forcePad.appendEscape(tableName) 39 | } 40 | 41 | def temporary: SQLBuiltQuery = { 42 | qb.pad 43 | .append(syntax.temporary) 44 | .forcePad.append(DefaultSQLSyntax.table) 45 | .forcePad.appendEscape(tableName) 46 | } 47 | } 48 | 49 | /** 50 | * This is the implementation of a root CREATE query, a wrapper around an abstract CREATE syntax block. 51 | * 52 | * This is used as the entry point to an SQL CREATE query, and it requires the user to provide "one more method" to fully specify a CREATE query. 53 | * The implicit conversion from a RootCreateQuery to a CreateQuery will automatically pick the "default" strategy below. 54 | * 55 | * @param table The table owning the record. 56 | * @param st The Abstract syntax block describing the possible decisions. 57 | * @param rowFunc The function used to map a result to a type-safe record. 58 | * @tparam T The type of the owning table. 59 | * @tparam R The type of the record. 60 | */ 61 | private[morpheus] class RootCreateQuery[ 62 | T <: BaseTable[T, _, TableRow], 63 | R, 64 | TableRow <: Row 65 | ](val table: T, val st: RootCreateSyntaxBlock, val rowFunc: TableRow => R) { 66 | 67 | protected[this] type BaseCreateQuery = CreateQuery[T, R, TableRow, Ungroupped, Unordered, Unlimited, Unchainned, AssignUnchainned, HNil] 68 | 69 | private[morpheus] def default: BaseCreateQuery = { 70 | new CreateQuery(table, st.default, rowFunc) 71 | } 72 | 73 | def ifNotExists: BaseCreateQuery = { 74 | new CreateQuery(table, st.ifNotExists, rowFunc) 75 | } 76 | 77 | def temporary: BaseCreateQuery = { 78 | new CreateQuery(table, st.temporary, rowFunc) 79 | } 80 | } 81 | 82 | private[morpheus] class DefaultRootCreateQuery[T <: BaseTable[T, _, DefaultRow], R] 83 | (table: T, st: RootCreateSyntaxBlock, rowFunc: DefaultRow => R) 84 | extends RootCreateQuery[T, R, DefaultRow](table, st, rowFunc) {} 85 | 86 | 87 | /** 88 | * This bit of magic allows all extending sub-classes to implement the "set" and "and" SQL clauses with all the necessary operators, 89 | * in a type safe way. By providing the third type argument and a custom way to subclass with the predetermined set of arguments, 90 | * all DSL representations of an UPDATE query can use the implementation without violating DRY. 91 | * 92 | * @tparam T The type of the table owning the record. 93 | * @tparam R The type of the record held in the table. 94 | */ 95 | class CreateQuery[T <: BaseTable[T, _, TableRow], 96 | R, 97 | TableRow <: Row, 98 | Group <: GroupBind, 99 | Order <: OrderBind, 100 | Limit <: LimitBind, 101 | Chain <: ChainBind, 102 | AssignChain <: AssignBind, 103 | Status <: HList 104 | ](table: T, init: SQLBuiltQuery, rowFunc: TableRow => R) extends Query[T, R, TableRow, Group, Order, Limit, Chain, AssignChain, Status](table, init, rowFunc) { 105 | 106 | override def query: SQLBuiltQuery = init 107 | 108 | protected[this] type QueryType[ 109 | G <: GroupBind, 110 | O <: OrderBind, 111 | L <: LimitBind, 112 | S <: ChainBind, 113 | C <: AssignBind, 114 | P <: HList 115 | ] = CreateQuery[T, R, TableRow, G, O, L, S, C, P] 116 | 117 | override protected[this] def create[ 118 | G <: GroupBind, 119 | O <: OrderBind, 120 | L <: LimitBind, 121 | S <: ChainBind, 122 | C <: AssignBind, 123 | P <: HList 124 | ](t: T, q: SQLBuiltQuery, r: TableRow => R): QueryType[G, O, L, S, C, P] = { 125 | new CreateQuery(t, q, r) 126 | } 127 | 128 | 129 | final protected[morpheus] def columnDefinitions: List[String] = { 130 | table.columns.foldRight(List.empty[String])((col, acc) => { 131 | col.qb.queryString :: acc 132 | }) 133 | } 134 | 135 | final protected[morpheus] def columnSchema[St <: HList]: CreateQuery[T, R, TableRow, Group, Order, Limit, Chain, AssignChain, St] = { 136 | new CreateQuery(table, init.append(columnDefinitions.mkString(", ")), rowFunc) 137 | } 138 | 139 | def ifNotExists: CreateQuery[T, R, TableRow, Group, Order, Limit, Chain, AssignChain, HNil] = { 140 | new CreateQuery(table, table.queryBuilder.ifNotExists(init), rowFunc) 141 | } 142 | 143 | def engine(engine: SQLEngine): Query[T, R, TableRow, Group, Order, Limit, Chain, AssignChain, HNil] = { 144 | new CreateQuery(table, 145 | table.queryBuilder.engine( 146 | init.wrap(columnDefinitions.mkString(", ")), 147 | engine.value 148 | ), 149 | rowFunc 150 | ) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /morpheus-dsl/src/main/scala/com/outworkers/morpheus/engine/query/DeleteQuery.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.engine.query 17 | 18 | import com.outworkers.morpheus.sql.DefaultRow 19 | import com.outworkers.morpheus.Row 20 | import com.outworkers.morpheus.builder.{AbstractSQLSyntax, AbstractSyntaxBlock, DefaultSQLSyntax, SQLBuiltQuery} 21 | import com.outworkers.morpheus.dsl.BaseTable 22 | import shapeless.{HList, HNil} 23 | 24 | import scala.annotation.implicitNotFound 25 | 26 | private[morpheus] class RootDeleteSyntaxBlock(query: String, tableName: String) extends AbstractSyntaxBlock { 27 | 28 | protected[this] val qb: SQLBuiltQuery = SQLBuiltQuery(query) 29 | 30 | def all: SQLBuiltQuery = { 31 | qb.pad.append(DefaultSQLSyntax.from) 32 | .forcePad.appendEscape(tableName) 33 | } 34 | 35 | def syntax: AbstractSQLSyntax = DefaultSQLSyntax 36 | } 37 | 38 | /** 39 | * This is the implementation of a root UPDATE query, a wrapper around an abstract syntax block. 40 | * 41 | * This is used as the entry point to an SQL query, and it requires the user to provide "one more method" to fully specify a SELECT query. 42 | * The implicit conversion from a RootSelectQuery to a SelectQuery will automatically pick the "all" strategy below. 43 | * 44 | * @param table The table owning the record. 45 | * @param st The Abstract syntax block describing the possible decisions. 46 | * @param rowFunc The function used to map a result to a type-safe record. 47 | * @tparam T The type of the owning table. 48 | * @tparam R The type of the record. 49 | */ 50 | private[morpheus] class RootDeleteQuery[ 51 | T <: BaseTable[T, _, TableRow], 52 | R, TableRow <: Row 53 | ](val table: T, val st: RootDeleteSyntaxBlock, val rowFunc: TableRow => R) { 54 | 55 | def fromRow(r: TableRow): R = rowFunc(r) 56 | 57 | protected[this] type BaseDeleteQuery = DeleteQuery[T, R, TableRow, Ungroupped, Unordered, Unlimited, Unchainned, AssignUnchainned, HNil] 58 | 59 | private[morpheus] final def all: BaseDeleteQuery = { 60 | new DeleteQuery(table, st.all, rowFunc) 61 | } 62 | } 63 | 64 | private[morpheus] class DefaultRootDeleteQuery[T <: BaseTable[T, _, DefaultRow], R] 65 | (table: T, st: RootDeleteSyntaxBlock, rowFunc: DefaultRow => R) 66 | extends RootDeleteQuery[T, R, DefaultRow](table, st, rowFunc) {} 67 | 68 | 69 | /** 70 | * This bit of magic allows all extending sub-classes to implement the "set" and "and" SQL clauses with all the necessary operators, 71 | * in a type safe way. By providing the third type argument and a custom way to subclass with the predetermined set of arguments, 72 | * all DSL representations of an UPDATE query can use the implementation without violating DRY. 73 | * 74 | * @tparam T The type of the table owning the record. 75 | * @tparam R The type of the record held in the table. 76 | */ 77 | class DeleteQuery[T <: BaseTable[T, _, TableRow], 78 | R, 79 | TableRow <: Row, 80 | Group <: GroupBind, 81 | Order <: OrderBind, 82 | Limit <: LimitBind, 83 | Chain <: ChainBind, 84 | AssignChain <: AssignBind, 85 | Status <: HList 86 | ](table: T, 87 | init: SQLBuiltQuery, 88 | rowFunc: TableRow => R 89 | ) extends Query[T, R, TableRow, Group, Order, Limit, Chain, AssignChain, Status](table, init, rowFunc) { 90 | 91 | protected[this] type QueryType[ 92 | G <: GroupBind, 93 | O <: OrderBind, 94 | L <: LimitBind, 95 | S <: ChainBind, 96 | C <: AssignBind, 97 | P <: HList 98 | ] = DeleteQuery[T, R, TableRow, G, O, L, S, C, P] 99 | 100 | override protected[this] def create[ 101 | G <: GroupBind, 102 | O <: OrderBind, 103 | L <: LimitBind, 104 | S <: ChainBind, 105 | C <: AssignBind, 106 | P <: HList 107 | ](t: T, q: SQLBuiltQuery, r: TableRow => R): QueryType[G, O, L, S, C, P] = { 108 | new DeleteQuery(t, q, r) 109 | } 110 | 111 | @implicitNotFound("You cannot use two where clauses on a single query") 112 | final def where(condition: T => QueryCondition)( 113 | implicit ev: Chain =:= Unchainned 114 | ): QueryType[Group, Order, Limit, Chainned, AssignChain, 115 | Status] = { 116 | new DeleteQuery(table, table.queryBuilder.where(init, condition(table).clause), rowFunc) 117 | } 118 | 119 | @implicitNotFound("You cannot use two where clauses on a single query") 120 | final def where(condition: QueryCondition)( 121 | implicit ev: Chain =:= Unchainned 122 | ): QueryType[Group, Order, Limit, Chainned, AssignChain, 123 | Status] = { 124 | new DeleteQuery(table, table.queryBuilder.where(init, condition.clause), rowFunc) 125 | } 126 | 127 | @implicitNotFound("You need to use the where method first") 128 | final def and(condition: T => QueryCondition)( 129 | implicit ev: Chain =:= Chainned 130 | ): QueryType[Group, Order, Limit, Chain, AssignChainned, 131 | Status] = { 132 | new DeleteQuery(table, table.queryBuilder.and(init, condition(table).clause), rowFunc) 133 | } 134 | 135 | @implicitNotFound("You need to use the where method first") 136 | final def and(condition: QueryCondition)( 137 | implicit ev: Chain =:= Chainned 138 | ): QueryType[Group, Order, Limit, Chain, AssignChainned, Status] 139 | = { 140 | new DeleteQuery(table, table.queryBuilder.and(init, condition.clause), rowFunc) 141 | } 142 | 143 | override protected[morpheus] def query: SQLBuiltQuery = init 144 | } -------------------------------------------------------------------------------- /morpheus-dsl/src/main/scala/com/outworkers/morpheus/engine/query/InsertQuery.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.engine.query 17 | 18 | import com.outworkers.morpheus.DataType 19 | import com.outworkers.morpheus.sql.DefaultRow 20 | import com.outworkers.morpheus.builder.{AbstractSQLSyntax, AbstractSyntaxBlock, DefaultSQLSyntax, SQLBuiltQuery} 21 | import com.outworkers.morpheus.column.AbstractColumn 22 | import com.outworkers.morpheus.dsl.BaseTable 23 | import com.outworkers.morpheus.engine.query.parts.{ColumnsPart, Defaults, LightweightPart, ValuePart} 24 | import com.outworkers.morpheus.Row 25 | import shapeless.{HList, HNil} 26 | 27 | import scala.annotation.implicitNotFound 28 | 29 | private[morpheus] class RootInsertSyntaxBlock(query: String, tableName: String) extends AbstractSyntaxBlock { 30 | 31 | protected[this] val qb = SQLBuiltQuery(query) 32 | 33 | def into: SQLBuiltQuery = { 34 | qb.forcePad.append(syntax.into) 35 | .forcePad.appendEscape(tableName) 36 | } 37 | 38 | override val syntax: AbstractSQLSyntax = DefaultSQLSyntax 39 | } 40 | 41 | /** 42 | * This is the implementation of a root UPDATE query, a wrapper around an abstract syntax block. 43 | * 44 | * This is used as the entry point to an SQL query, and it requires the user to provide "one more method" to fully specify a SELECT query. 45 | * The implicit conversion from a RootSelectQuery to a SelectQuery will automatically pick the "all" strategy below. 46 | * 47 | * @param table The table owning the record. 48 | * @param st The Abstract syntax block describing the possible decisions. 49 | * @param rowFunc The function used to map a result to a type-safe record. 50 | * @tparam T The type of the owning table. 51 | * @tparam R The type of the record. 52 | */ 53 | private[morpheus] class RootInsertQuery[T <: BaseTable[T, _, TableRow], R, TableRow <: Row](val table: T, val st: RootInsertSyntaxBlock, val rowFunc: 54 | TableRow => R) { 55 | 56 | def fromRow(r: TableRow): R = rowFunc(r) 57 | 58 | private[morpheus] def into: InsertQuery[T, R, TableRow, Ungroupped, Unordered, Unlimited, Unchainned, AssignUnchainned, HNil] = { 59 | new InsertQuery(table, st.into, rowFunc) 60 | } 61 | } 62 | 63 | private[morpheus] class DefaultRootInsertQuery[T <: BaseTable[T, _, DefaultRow], R] 64 | (table: T, st: RootInsertSyntaxBlock, rowFunc: DefaultRow => R) 65 | extends RootInsertQuery[T, R, DefaultRow](table, st, rowFunc) {} 66 | 67 | 68 | class InsertQuery[ 69 | T <: BaseTable[T, _, TableRow], 70 | R, 71 | TableRow <: Row, 72 | Group <: GroupBind, 73 | Order <: OrderBind, 74 | Limit <: LimitBind, 75 | Chain <: ChainBind, 76 | AssignChain <: AssignBind, 77 | Status <: HList 78 | ](table: T, 79 | init: SQLBuiltQuery, 80 | rowFunc: TableRow => R, 81 | columnsPart: ColumnsPart = Defaults.EmptyColumnsPart, 82 | valuePart: ValuePart = Defaults.EmptyValuePart, 83 | lightweightPart: LightweightPart = Defaults.EmptyLightweightPart 84 | ) extends Query[T, R, TableRow, Group, Order, Limit, Chain, AssignChain, Status](table, init, rowFunc) { 85 | 86 | protected[this] type QueryType[ 87 | G <: GroupBind, 88 | O <: OrderBind, 89 | L <: LimitBind, 90 | S <: ChainBind, 91 | C <: AssignBind, 92 | P <: HList 93 | ] = InsertQuery[T, R, TableRow, G, O, L, S, C, P] 94 | 95 | override protected[this] def create[ 96 | G <: GroupBind, 97 | O <: OrderBind, 98 | L <: LimitBind, 99 | S <: ChainBind, 100 | C <: AssignBind, 101 | P <: HList 102 | ](t: T, q: SQLBuiltQuery, r: TableRow => R): QueryType[G, O, L, S, C, P] = { 103 | new InsertQuery(t, q, r, columnsPart, valuePart, lightweightPart) 104 | } 105 | 106 | /** 107 | * At this point you may be reading and thinking "WTF", but fear not, it all makes sense. Every call to a "value method" will generate a new Insert Query, 108 | * but the list of statements in the new query will include a new (String, String) pair, where the first part is the column name and the second one is the 109 | * serialised value. This is a very simple accumulator that will eventually allow calling the "insert" method on a queryBuilder to produce the final 110 | * serialisation result, a hopefully valid MySQL insert query. 111 | * 112 | * @param insertion The insert condition is a pair of a column with the value to use for it. It looks like this: value(_.someColumn, someValue), 113 | * where the assignment is of course type safe. 114 | * @param obj The object is the value to use for the column. 115 | * @tparam RR The SQL primitive or rather it's Scala correspondent to use at this time. 116 | * @return A new InsertQuery, where the list of statements in the Insert has been chained and updated for serialisation. 117 | */ 118 | @implicitNotFound(msg = "To use the value method this query needs to be an insert query and the query needs to be HNil. You probably have more " + 119 | "value calls than columns in your table, which would result in an invalid MySQL query.") 120 | def value[RR : DataType]( 121 | insertion: T => AbstractColumn[RR], 122 | obj: RR 123 | ): QueryType[Group, Order, Limit, Chain, AssignChain, Status] = { 124 | new InsertQuery[T, R, TableRow, Group, Order, Limit, Chain, AssignChain, Status]( 125 | table, 126 | init, 127 | fromRow, 128 | columnsPart append SQLBuiltQuery(insertion(table).name), 129 | valuePart append SQLBuiltQuery(implicitly[DataType[RR]].serialize(obj)), 130 | lightweightPart 131 | ) 132 | } 133 | 134 | override def query: SQLBuiltQuery = (columnsPart merge valuePart merge lightweightPart) build init 135 | } 136 | -------------------------------------------------------------------------------- /morpheus-dsl/src/main/scala/com/outworkers/morpheus/engine/query/Query.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.engine.query 17 | 18 | import com.outworkers.morpheus.Row 19 | import com.outworkers.morpheus.builder.SQLBuiltQuery 20 | import com.outworkers.morpheus.column.SelectColumn 21 | import com.outworkers.morpheus.dsl.BaseTable 22 | import shapeless.HList 23 | 24 | import scala.annotation.implicitNotFound 25 | 26 | /** 27 | * This bit of magic allows all extending sub-classes to implement the "where" and "and" SQL clauses with all the necessary operators, 28 | * in a type safe way. By providing the third type argument and a custom way to subclass with the predetermined set of arguments, all queries such as UPDATE, 29 | * DELETE, ALTER and so on can use the same root implementation of clauses and therefore avoid the violation of DRY. 30 | * 31 | * The reason why the "clause" and "andClause" methods below are protected is so that extending classes can decide when and how to expose "where" and "and" 32 | * SQL methods to the DSL user. Used mainly to make queries like "select.where(_.a = b).where(_.c = d)" impossible, 33 | * or in other words make illegal programming states unrepresentable. There is an awesome book about how to do this in Scala, 34 | * I will link to it as soon as the book is published. 35 | * 36 | * @param table The table owning the record. 37 | * @param init The root SQL query to start building from. 38 | * @param rowFunc The function mapping a row to a record. 39 | * @tparam T The type of the table owning the record. 40 | * @tparam R The type of the record held in the table. 41 | */ 42 | abstract class Query[T <: BaseTable[T, _, TableRow], 43 | R, 44 | TableRow <: Row, 45 | Group <: GroupBind, 46 | Ord <: OrderBind, 47 | Lim <: LimitBind, 48 | Chain <: ChainBind, 49 | AC <: AssignBind, 50 | PS <: HList 51 | ]( 52 | val table: T, 53 | val init: SQLBuiltQuery, 54 | val rowFunc: TableRow => R 55 | ) extends SQLQuery[T, R, TableRow] { 56 | 57 | protected[this] type QueryType[ 58 | G <: GroupBind, 59 | O <: OrderBind, 60 | L <: LimitBind, 61 | S <: ChainBind, 62 | C <: AssignBind, 63 | P <: HList 64 | ] <: Query[T, R, TableRow, G, O, L, S, C, P] 65 | 66 | protected[this] def create[ 67 | G <: GroupBind, 68 | O <: OrderBind, 69 | L <: LimitBind, 70 | S <: ChainBind, 71 | C <: AssignBind, 72 | P <: HList 73 | ](t: T, q: SQLBuiltQuery, r: TableRow => R): QueryType[G, O, L, S, C, P] 74 | 75 | def fromRow(row: TableRow): R = rowFunc(row) 76 | 77 | @implicitNotFound("You cannot set two limits on the same query") 78 | final def limit(value: Int)(implicit ev: Lim =:= Unlimited): QueryType[Group, Ord, Limited, Chain, AC, PS] = { 79 | create(table, table.queryBuilder.limit(query, value.toString), rowFunc) 80 | } 81 | 82 | @implicitNotFound("You cannot ORDER a query more than once") 83 | final def orderBy(conditions: (T => QueryOrder)*)( 84 | implicit ev: Ord =:= Unordered 85 | ): QueryType[Group, Ordered, Lim, Chain, AC, PS] = { 86 | val applied = conditions map { 87 | fn => fn(table).clause 88 | } 89 | 90 | create(table, table.queryBuilder.orderBy(query, applied), rowFunc) 91 | } 92 | 93 | @implicitNotFound("You cannot GROUP a query more than once or GROUP after you ORDER a query") 94 | final def groupBy(columns: (T => SelectColumn[_])*)( 95 | implicit ev1: Group =:= Ungroupped, 96 | ev2: Ord =:= Unordered 97 | ): QueryType[Groupped, Ord, Lim, Chain, AC, PS] = { 98 | create(table, table.queryBuilder.groupBy(query, columns map { _(table).queryString }), rowFunc) 99 | } 100 | 101 | } 102 | -------------------------------------------------------------------------------- /morpheus-dsl/src/main/scala/com/outworkers/morpheus/engine/query/SQLEngine.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.engine.query 17 | 18 | object DefaultMySQLEngines { 19 | val InnoDB = "InnoDB" 20 | val Memory = "MEMORY" 21 | val Heap = "HEAP" 22 | val Merge = "MERGE" 23 | val MrgMyLSAM = "MRG_MYISAM" 24 | val isam = "ISAM" 25 | val MrgISAM = "MRG_ISAM" 26 | val innoBase = "INNOBASE" 27 | val BDB = "BDB" 28 | val BerkleyDB = "BERKELEYDB" 29 | val NDBCluster = "NDBCLUSTER" 30 | val NDB = "NDB" 31 | val Example = "EXAMPLE" 32 | val Archive = "ARCHIVE" 33 | val CSV = "CSV" 34 | val Federated = "FEDERATED" 35 | val BlackHole = "BLACKHOLE" 36 | } 37 | 38 | sealed abstract class SQLEngine(val value: String) 39 | 40 | /** 41 | * This is the sequence of default available storage engines in the MySQL 5.0 specification. 42 | * For the official documentation, @see the MySQL 5.0 docs. 43 | * 44 | * More recent versions of MySQL features far less available options. The official list is available on @see the MySQL 5.7 docs page. 46 | */ 47 | trait DefaultSQLEngines { 48 | case object InnoDB extends SQLEngine(DefaultMySQLEngines.InnoDB) 49 | case object InnoBase extends SQLEngine(DefaultMySQLEngines.innoBase) 50 | case object Memory extends SQLEngine(DefaultMySQLEngines.Memory) 51 | case object Heap extends SQLEngine(DefaultMySQLEngines.Heap) 52 | case object Merge extends SQLEngine(DefaultMySQLEngines.Merge) 53 | case object BDB extends SQLEngine(DefaultMySQLEngines.BDB) 54 | case object BerkleyDB extends SQLEngine(DefaultMySQLEngines.BerkleyDB) 55 | case object NDBCluster extends SQLEngine(DefaultMySQLEngines.NDBCluster) 56 | case object NDB extends SQLEngine(DefaultMySQLEngines.NDB) 57 | case object Example extends SQLEngine(DefaultMySQLEngines.Example) 58 | case object Archive extends SQLEngine(DefaultMySQLEngines.Archive) 59 | case object CSV extends SQLEngine(DefaultMySQLEngines.CSV) 60 | case object Federated extends SQLEngine(DefaultMySQLEngines.Federated) 61 | case object Blackhole extends SQLEngine(DefaultMySQLEngines.BlackHole) 62 | 63 | } 64 | 65 | trait MySQLEngines extends DefaultSQLEngines {} 66 | -------------------------------------------------------------------------------- /morpheus-dsl/src/main/scala/com/outworkers/morpheus/engine/query/SQLQuery.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.engine.query 17 | 18 | import com.twitter.util.Future 19 | import com.outworkers.morpheus.builder.SQLBuiltQuery 20 | import com.outworkers.morpheus.dsl.{BaseTable, ResultSetOperations} 21 | import com.outworkers.morpheus.{Client, Result, Row} 22 | 23 | import scala.concurrent.{Future => ScalaFuture} 24 | 25 | object MySQLManager 26 | 27 | trait SQLQuery[T <: BaseTable[T, _, TableRow], R, TableRow <: Row] extends ResultSetOperations { 28 | protected[morpheus] def query: SQLBuiltQuery 29 | 30 | /** 31 | * A simple forwarding method to prevent some extra boiler-plate during tests. 32 | * This will serialise an existing query to the relevant SQL string. 33 | * @return A string representing the query encoded in SQL. 34 | */ 35 | def queryString: String = query.appendIfAbsent(";").queryString 36 | 37 | /** 38 | * This method is used when the query is not returning a data result, such as an UPDATE query. 39 | * While it will accurately monitor the execution of the query and the Future will complete when the task is "done" in SQL, 40 | * the type-safe mapping of the result is not necessary at this point. 41 | * 42 | * This method duplicates the API to provide an alternative to people who don't use Twitter Futures or any Twitter libraries in their stack. Until now 43 | * anyway. Twitter has been gossiping about making com.twitter.util.Future extend scala.concurrent.Future, but until such times a dual API is best. 44 | * 45 | * The below implementation will pass type arguments explicitly to the covariant constructor. 46 | * 47 | * @param client The Finagle MySQL client in the scope of which to execute the query. 48 | * @return A Scala Future wrapping a default Finagle MySQL query result object. 49 | */ 50 | def future[DBRow <: Row, DBResult <: Result]()(implicit client: Client[DBRow, DBResult]): ScalaFuture[DBResult] = { 51 | twitterToScala(execute()) 52 | } 53 | 54 | /** 55 | * This method is used when the query is not returning a data result, such as an UPDATE query. 56 | * While it will accurately monitor the execution of the query and the Future will complete when the task is "done" in SQL, 57 | * the type-safe mapping of the result is not necessary at this point. 58 | * 59 | * The below implementation will pass type arguments explicitly to the covariant constructor. 60 | * 61 | * @param client The Finagle MySQL client in the scope of which to execute the query. 62 | * @return A Scala Future wrapping a default Finagle MySQL query result object. 63 | */ 64 | def execute[DBRow <: Row, DBResult <: Result]()(implicit client: Client[DBRow, DBResult]): Future[DBResult] = { 65 | queryToFuture[DBRow, DBResult](queryString) 66 | } 67 | 68 | } 69 | 70 | 71 | trait SQLResultsQuery[ 72 | T <: BaseTable[T, _, DBRow], 73 | R, 74 | DBRow <: Row, 75 | DBResult <: Result, 76 | Limit <: LimitBind 77 | ] extends SQLQuery[T, R, DBRow] { 78 | 79 | def fromRow(r: DBRow): R 80 | 81 | /** 82 | * Returns the first row from the select ignoring everything else. 83 | * @param client The MySQL client in use. 84 | * @return 85 | */ 86 | def one()(implicit client: Client[DBRow, DBResult], ev: Limit =:= Unlimited): ScalaFuture[Option[R]] 87 | 88 | /** 89 | * Get the result of an operation as a Twitter Future. 90 | * @param client The MySQL client in use. 91 | * @return A Twitter future wrapping the result. 92 | */ 93 | def get()(implicit client: Client[DBRow, DBResult], ev: Limit =:= Unlimited): Future[Option[R]] 94 | 95 | def fetch()(implicit client: Client[DBRow, DBResult]): ScalaFuture[Seq[R]] = { 96 | twitterToScala(client.select(query.queryString)(fromRow)) 97 | } 98 | 99 | def collect()(implicit client: Client[DBRow, DBResult]): Future[Seq[R]] = { 100 | client.select(query.queryString)(fromRow) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /morpheus-dsl/src/main/scala/com/outworkers/morpheus/engine/query/package.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.engine 17 | 18 | package object query { 19 | sealed trait GroupBind 20 | final abstract class Groupped extends GroupBind 21 | final abstract class Ungroupped extends GroupBind 22 | 23 | sealed trait ChainBind 24 | final abstract class Chainned extends ChainBind 25 | final abstract class Unchainned extends ChainBind 26 | 27 | sealed trait OrderBind 28 | final abstract class Ordered extends OrderBind 29 | final abstract class Unordered extends OrderBind 30 | 31 | sealed trait LimitBind 32 | final abstract class Limited extends LimitBind 33 | final abstract class Unlimited extends LimitBind 34 | 35 | } 36 | -------------------------------------------------------------------------------- /morpheus-dsl/src/main/scala/com/outworkers/morpheus/engine/query/parts/Part.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.engine.query.parts 17 | 18 | import com.outworkers.morpheus.builder.SQLBuiltQuery 19 | 20 | abstract class QueryPart[T <: QueryPart[T]](val list: List[SQLBuiltQuery] = Nil) { 21 | 22 | def instance(l: List[SQLBuiltQuery]): T 23 | 24 | def nonEmpty: Boolean = list.nonEmpty 25 | 26 | def qb: SQLBuiltQuery 27 | 28 | def build(init: SQLBuiltQuery): SQLBuiltQuery = if (init.nonEmpty) { 29 | qb.bpad.prepend(init) 30 | } else { 31 | qb.prepend(init) 32 | } 33 | 34 | def append(q: SQLBuiltQuery): T = instance(list ::: (q :: Nil)) 35 | 36 | def append(q: SQLBuiltQuery*): T = instance(q.toList ::: list) 37 | 38 | def append(q: List[SQLBuiltQuery]): T = instance(q ::: list) 39 | 40 | def mergeList(list: List[SQLBuiltQuery]): MergeList 41 | 42 | def merge[X <: QueryPart[X]](part: X): MergeList = { 43 | val list = if (part.qb.nonEmpty) List(qb, part.qb) else List(qb) 44 | 45 | mergeList(list) 46 | } 47 | } 48 | 49 | 50 | case class MergeList(list: List[SQLBuiltQuery]) { 51 | 52 | def this(query: SQLBuiltQuery) = this(List(query)) 53 | 54 | def nonEmpty: Boolean = list.nonEmpty 55 | 56 | def apply(list: List[SQLBuiltQuery]): MergeList = new MergeList(list) 57 | 58 | def build: SQLBuiltQuery = SQLBuiltQuery(list.map(_.queryString).mkString(" ")) 59 | 60 | /** 61 | * This will build a merge list into a final executable query. 62 | * It will also prepend the CQL query passed as a parameter to the final string. 63 | * 64 | * If the current list has only empty queries to merge, the init string is return instead. 65 | * Alternatively, the init string is prepended after a single space. 66 | * 67 | * @param init The initialisation query of the part merge. 68 | * @return A final, executable CQL query with all the parts merged. 69 | */ 70 | def build(init: SQLBuiltQuery): SQLBuiltQuery = if (list.exists(_.nonEmpty)) { 71 | build.bpad.prepend(init.queryString) 72 | } else { 73 | init 74 | } 75 | 76 | def merge[X <: QueryPart[X]](part: X, init: SQLBuiltQuery = SQLBuiltQuery.empty): MergeList = { 77 | val appendable = part build init 78 | 79 | if (appendable.nonEmpty) { 80 | apply(list ::: List(appendable)) 81 | } else { 82 | this 83 | } 84 | } 85 | } 86 | 87 | object MergeList { 88 | def empty: MergeList = new MergeList(Nil) 89 | } -------------------------------------------------------------------------------- /morpheus-dsl/src/main/scala/com/outworkers/morpheus/engine/query/parts/QueryPart.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.engine.query.parts 17 | 18 | import com.outworkers.morpheus.builder.{DefaultQueryBuilder, SQLBuiltQuery} 19 | 20 | abstract class SQLQueryPart[ 21 | Part <: SQLQueryPart[Part] 22 | ](override val list: List[SQLBuiltQuery]) extends QueryPart[Part](list) { 23 | override def mergeList(list: List[SQLBuiltQuery]): MergeList = new MergeList(list) 24 | } 25 | 26 | sealed class WherePart(override val list: List[SQLBuiltQuery] = Nil) extends SQLQueryPart[WherePart](list) { 27 | override def qb: SQLBuiltQuery = DefaultQueryBuilder.clauses(list) 28 | 29 | override def instance(list: List[SQLBuiltQuery]): WherePart = new WherePart(list) 30 | } 31 | 32 | sealed class LimitedPart(override val list: List[SQLBuiltQuery] = Nil) extends SQLQueryPart[LimitedPart](list) { 33 | override def qb: SQLBuiltQuery = DefaultQueryBuilder.clauses(list) 34 | 35 | override def instance(l: List[SQLBuiltQuery]): LimitedPart = new LimitedPart(l) 36 | } 37 | 38 | sealed class OrderPart(override val list: List[SQLBuiltQuery] = Nil) extends SQLQueryPart[OrderPart](list) { 39 | override def qb: SQLBuiltQuery = DefaultQueryBuilder.clauses(list) 40 | 41 | override def instance(l: List[SQLBuiltQuery]): OrderPart = new OrderPart(l) 42 | } 43 | 44 | sealed class SetPart(override val list: List[SQLBuiltQuery] = Nil) extends SQLQueryPart[SetPart](list) { 45 | override def qb: SQLBuiltQuery = DefaultQueryBuilder.clauses(list) 46 | 47 | override def instance(l: List[SQLBuiltQuery]): SetPart = new SetPart(l) 48 | } 49 | 50 | sealed class ColumnsPart(override val list: List[SQLBuiltQuery] = Nil) extends SQLQueryPart[ColumnsPart](list) { 51 | override def qb: SQLBuiltQuery = DefaultQueryBuilder.columns(list) 52 | 53 | override def instance(l: List[SQLBuiltQuery]): ColumnsPart = new ColumnsPart(l) 54 | } 55 | 56 | sealed class ValuePart(override val list: List[SQLBuiltQuery] = Nil) extends SQLQueryPart[ValuePart](list) { 57 | override def qb: SQLBuiltQuery = DefaultQueryBuilder.values(list) 58 | 59 | override def instance(l: List[SQLBuiltQuery]): ValuePart = new ValuePart(l) 60 | } 61 | 62 | sealed class LightweightPart(override val list: List[SQLBuiltQuery] = Nil) extends SQLQueryPart[LightweightPart](list) { 63 | override def qb: SQLBuiltQuery = DefaultQueryBuilder.clauses(list) 64 | 65 | override def instance(l: List[SQLBuiltQuery]): LightweightPart = new LightweightPart(l) 66 | } 67 | 68 | private[morpheus] object Defaults { 69 | val EmptyWherePart = new WherePart() 70 | val EmptySetPart = new SetPart() 71 | val EmptyLimitPart = new LimitedPart() 72 | val EmptyOrderPart = new OrderPart() 73 | val EmptyValuePart = new ValuePart() 74 | val EmptyColumnsPart = new ColumnsPart() 75 | val EmptyLightweightPart = new LightweightPart() 76 | } 77 | -------------------------------------------------------------------------------- /morpheus-dsl/src/main/scala/com/outworkers/morpheus/keys/Key.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.keys 17 | 18 | import com.outworkers.morpheus.column.NumericColumn 19 | import com.outworkers.morpheus.builder.{DefaultSQLSyntax, SQLBuiltQuery} 20 | import com.outworkers.morpheus.column.AbstractColumn 21 | 22 | private[morpheus] trait Key[KeyType <: Key[KeyType]] { self: AbstractColumn[_] => 23 | 24 | override def qb: SQLBuiltQuery = { 25 | SQLBuiltQuery(name).pad.append(sqlType) 26 | .forcePad.append(keyToQueryString) 27 | .pad.append(notNull match { 28 | case true => DefaultSQLSyntax.notNull 29 | case false => "" 30 | }).pad.append(autoIncrement match { 31 | case true => DefaultSQLSyntax.autoIncrement 32 | case false => "" 33 | }).trim 34 | } 35 | 36 | 37 | protected[this] def keyToQueryString: String 38 | protected[this] val autoIncrement = false 39 | } 40 | 41 | trait PrimaryKey extends Key[PrimaryKey] { 42 | self: AbstractColumn[_] => 43 | 44 | protected[this] def keyToQueryString = DefaultSQLSyntax.primaryKey 45 | } 46 | 47 | trait UniqueKey extends Key[UniqueKey] { 48 | self: AbstractColumn[_] => 49 | 50 | protected[this] def keyToQueryString = DefaultSQLSyntax.uniqueKey 51 | 52 | } 53 | 54 | trait NotNull { self: AbstractColumn[_] => 55 | 56 | override def notNull: Boolean = true 57 | } 58 | 59 | trait Autoincrement { self: AbstractColumn[Int] with PrimaryKey => 60 | 61 | override protected[this] val autoIncrement = true 62 | } 63 | 64 | trait Zerofill extends Key[Zerofill] { self: NumericColumn[_, _, _, _] => 65 | 66 | override def qb: SQLBuiltQuery = { 67 | SQLBuiltQuery(name).pad.append(sqlType) 68 | .forcePad.append(keyToQueryString) 69 | .pad.append(unsigned match { 70 | case true => DefaultSQLSyntax.unsigned 71 | case false => "" 72 | }) 73 | .pad.append(notNull match { 74 | case true => DefaultSQLSyntax.notNull 75 | case false => "" 76 | }).pad.append(autoIncrement match { 77 | case true => DefaultSQLSyntax.autoIncrement 78 | case false => "" 79 | }).trim 80 | } 81 | 82 | protected[this] def keyToQueryString = DefaultSQLSyntax.zeroFill 83 | 84 | } 85 | 86 | trait Unsigned[ValueType] { self: NumericColumn[_, _, _, ValueType] => 87 | 88 | override def unsigned: Boolean = true 89 | } 90 | -------------------------------------------------------------------------------- /morpheus-dsl/src/main/scala/com/outworkers/morpheus/sql/Columns.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.sql 17 | 18 | import com.outworkers.morpheus.column._ 19 | import shapeless.{<:!<, =:!=} 20 | 21 | trait SqlKeys { 22 | 23 | abstract class ForeignKey[T <: BaseTable[T, R, DefaultRow], R, T1 <: BaseTable[T1, _, DefaultRow]] 24 | (origin: T, columns: IndexColumn#NonIndexColumn[T1]*) 25 | (implicit ev: T =:!= T1, ev2: IndexColumn#NonIndexColumn[T1] <:!< IndexColumn) extends AbstractForeignKey[T, R, DefaultRow, T1](origin, columns: _*) 26 | 27 | class Index[T <: BaseTable[T, R, DefaultRow], R](columns: IndexColumn#NonIndexColumn[_]*)(implicit ev: 28 | IndexColumn#NonIndexColumn[_] <:!< IndexColumn) extends AbstractIndex[T, R, DefaultRow](columns: _*) 29 | 30 | } 31 | 32 | 33 | trait SqlPrimitiveColumns { 34 | class LongColumn[T <: BaseTable[T, R, DefaultRow], R](t: BaseTable[T, R, DefaultRow])(implicit ev: DataType[Long]) 35 | extends AbstractLongColumn[T, R, DefaultRow](t) 36 | } 37 | 38 | 39 | trait SqlColumns { 40 | 41 | class IntColumn[T <: BaseTable[T, R, DefaultRow], R](t: BaseTable[T, R, DefaultRow], limit: Int = 0)(implicit ev: DataType[Int]) 42 | extends AbstractIntColumn[T, R, DefaultRow](t, limit) 43 | 44 | class MediumIntColumn[T <: BaseTable[T, R, DefaultRow], R](t: BaseTable[T, R, DefaultRow], limit: Int = 0)(implicit ev: DataType[Int]) 45 | extends AbstractMediumIntColumn[T, R, DefaultRow](t, limit) 46 | 47 | class SmallIntColumn[T <: BaseTable[T, R, DefaultRow], R](t: BaseTable[T, R, DefaultRow], limit: Int = 0)(implicit ev: DataType[Int]) 48 | extends AbstractSmallIntColumn[T, R, DefaultRow](t, limit) 49 | 50 | class TinyIntColumn[T <: BaseTable[T, R, DefaultRow], R](t: BaseTable[T, R, DefaultRow], limit: Int = 0)(implicit ev: DataType[Int]) 51 | extends AbstractTinyIntColumn[T, R, DefaultRow](t, limit) 52 | 53 | class YearColumn[T <: BaseTable[T, R, DefaultRow], R](t: BaseTable[T, R, DefaultRow])(implicit ev: DataType[Int]) 54 | extends AbstractYearColumn[T, R, DefaultRow](t) 55 | 56 | 57 | class BlobColumn[T <: BaseTable[T, R, DefaultRow], R](t: BaseTable[T, R, DefaultRow], limit: Int = 0)(implicit ev: DataType[String]) 58 | extends AbstractBlobColumn[T, R, DefaultRow](t, limit) 59 | 60 | class LongTextColumn[T <: BaseTable[T, R, DefaultRow], R](t: BaseTable[T, R, DefaultRow], limit: Int = KnownTypeLimits.longTextLimit)(implicit ev: DataType[String]) 61 | extends AbstractLongTextColumn[T, R, DefaultRow](t, limit) 62 | 63 | class MediumBlobColumn[T <: BaseTable[T, R, DefaultRow], R](t: BaseTable[T, R, DefaultRow], limit: Int = 0)(implicit ev: DataType[String]) 64 | extends AbstractMediumBlobColumn[T, R, DefaultRow](t, limit) 65 | 66 | class MediumTextColumn[T <: BaseTable[T, R, DefaultRow], R](t: BaseTable[T, R, DefaultRow], limit: Int = KnownTypeLimits.mediumTextLimit)(implicit ev: DataType[String]) 67 | extends AbstractMediumTextColumn[T, R, DefaultRow](t, limit) 68 | 69 | class TextColumn[T <: BaseTable[T, R, DefaultRow], R](t: BaseTable[T, R, DefaultRow], limit: Int = KnownTypeLimits.textLimit)(implicit ev: DataType[String]) 70 | extends AbstractTextColumn[T, R, DefaultRow](t) 71 | 72 | class TinyBlobColumn[T <: BaseTable[T, R, DefaultRow], R](t: BaseTable[T, R, DefaultRow], limit: Int = 0)(implicit ev: DataType[String]) 73 | extends AbstractTinyBlobColumn[T, R, DefaultRow](t, limit) 74 | 75 | class TinyTextColumn[T <: BaseTable[T, R, DefaultRow], R](t: BaseTable[T, R, DefaultRow], limit: Int = 0)(implicit ev: DataType[String]) 76 | extends AbstractTinyTextColumn[T, R, DefaultRow](t, limit) 77 | 78 | class VarcharColumn[T <: BaseTable[T, R, DefaultRow], R](t: BaseTable[T, R, DefaultRow], limit: Int = KnownTypeLimits.varcharLimit)(implicit ev: DataType[String]) 79 | extends AbstractVarcharColumn[T, R, DefaultRow](t, limit) 80 | 81 | class CharColumn[T <: BaseTable[T, R, DefaultRow], R](t: BaseTable[T, R, DefaultRow], limit: Int = KnownTypeLimits.charLimit)(implicit ev: DataType[String]) 82 | extends AbstractCharColumn[T, R, DefaultRow](t, limit) 83 | 84 | class LongBlobColumn[T <: BaseTable[T, R, DefaultRow], R](t: BaseTable[T, R, DefaultRow], limit: Int = 0)(implicit ev: DataType[String]) 85 | extends AbstractLongBlobColumn[T, R, DefaultRow](t, limit) 86 | } 87 | -------------------------------------------------------------------------------- /morpheus-dsl/src/main/scala/com/outworkers/morpheus/sql/SQLDatabase.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.sql 17 | -------------------------------------------------------------------------------- /morpheus-dsl/src/main/scala/com/outworkers/morpheus/sql/SQLTable.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.sql 17 | 18 | import com.outworkers.morpheus.builder.{DefaultQueryBuilder, DefaultSQLSyntax} 19 | import com.outworkers.morpheus.dsl.SelectTable 20 | import com.outworkers.morpheus.engine.query._ 21 | import com.outworkers.morpheus.{Row => MorpheusRow} 22 | 23 | trait DefaultRow extends MorpheusRow 24 | 25 | private[morpheus] class MySQLRootSelectQuery[ 26 | T <: BaseTable[T, _, DefaultRow], 27 | R 28 | ](table: T, st: AbstractSelectSyntaxBlock, rowFunc: DefaultRow => R) 29 | extends AbstractRootSelectQuery[T, R, DefaultRow](table, st, rowFunc) { 30 | } 31 | 32 | abstract class SQLTable[ 33 | Owner <: BaseTable[Owner, Record, DefaultRow], 34 | Record 35 | ] extends BaseTable[Owner, Record, DefaultRow] with SelectTable[Owner, Record, DefaultRow, DefaultRootSelectQuery, AbstractSelectSyntaxBlock] { 36 | 37 | val queryBuilder = DefaultQueryBuilder 38 | 39 | val syntax = DefaultSQLSyntax 40 | 41 | protected[this] def createRootSelect[ 42 | A <: BaseTable[A, _, DefaultRow], 43 | B 44 | ](table: A, block: AbstractSelectSyntaxBlock, rowFunc: DefaultRow => B): DefaultRootSelectQuery[A, B] = { 45 | new DefaultRootSelectQuery[A, B](table, block, rowFunc) 46 | } 47 | 48 | protected[this] def selectBlock( 49 | query: String, 50 | tableName: String, 51 | cols: List[String] = List("*") 52 | ): AbstractSelectSyntaxBlock = { 53 | new AbstractSelectSyntaxBlock(query, tableName, cols) 54 | } 55 | 56 | def update: DefaultRootUpdateQuery[Owner, Record] = new DefaultRootUpdateQuery( 57 | this.asInstanceOf[Owner], 58 | new RootUpdateSyntaxBlock(syntax.update, tableName), 59 | fromRow 60 | ) 61 | 62 | def delete: DefaultRootDeleteQuery[Owner, Record] = new DefaultRootDeleteQuery( 63 | this.asInstanceOf[Owner], 64 | new RootDeleteSyntaxBlock(syntax.delete, tableName), 65 | fromRow 66 | ) 67 | 68 | def insert: DefaultRootInsertQuery[Owner, Record] = new DefaultRootInsertQuery( 69 | this.asInstanceOf[Owner], 70 | new RootInsertSyntaxBlock(syntax.insert, tableName), 71 | fromRow 72 | ) 73 | 74 | def create: DefaultRootCreateQuery[Owner, Record] = new DefaultRootCreateQuery( 75 | this.asInstanceOf[Owner], 76 | new RootCreateSyntaxBlock(syntax.create, tableName), 77 | fromRow 78 | ) 79 | 80 | } 81 | -------------------------------------------------------------------------------- /morpheus-dsl/src/main/scala/com/outworkers/morpheus/sql/package.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus 17 | 18 | import com.outworkers.morpheus.column.AbstractColumn 19 | import com.outworkers.morpheus.dsl.DefaultImportsDefinition 20 | import com.outworkers.morpheus.operators.SQLOperatorSet 21 | import com.outworkers.morpheus.engine.query.{DefaultSQLEngines, SQLQueryColumn} 22 | 23 | package object sql extends DefaultImportsDefinition 24 | with DefaultDataTypes 25 | with DefaultSQLEngines 26 | with SQLOperatorSet 27 | with SqlColumns 28 | with SqlPrimitiveColumns 29 | with SqlKeys 30 | with DefaultSQLImplicits { 31 | 32 | override implicit def columnToQueryColumn[T : DataType](col: AbstractColumn[T]): SQLQueryColumn[T] = new SQLQueryColumn[T](col) 33 | 34 | type Table[Owner <: BaseTable[Owner, Record, DefaultRow], Record] = SQLTable[Owner, Record] 35 | 36 | 37 | type Row = DefaultRow 38 | 39 | } 40 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/CustomSamplers.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus 17 | 18 | import java.sql.{Date => SqlDate} 19 | import java.util.Date 20 | 21 | import org.joda.time.{DateTime, DateTimeZone} 22 | import org.scalacheck.{Arbitrary, Gen} 23 | 24 | import scala.math.BigDecimal.RoundingMode 25 | 26 | trait CustomSamplers { 27 | val offset = 10000 28 | 29 | implicit val dateGen: Arbitrary[Date] = Arbitrary(Gen.delay(new Date(new DateTime(DateTimeZone.UTC).getMillis))) 30 | 31 | implicit val sqlDateGen: Arbitrary[SqlDate] = Arbitrary(Gen.delay(new SqlDate(new DateTime(DateTimeZone.UTC).getMillis))) 32 | 33 | implicit val floatGen: Arbitrary[Float] = Arbitrary(Arbitrary.arbFloat.arbitrary.map(fl => BigDecimal(fl).setScale(2, RoundingMode.HALF_UP).toFloat)) 34 | 35 | implicit val jodaGen: Arbitrary[DateTime] = Arbitrary { 36 | for { 37 | offset <- Gen.choose(-offset, offset) 38 | now = DateTime.now(DateTimeZone.UTC) 39 | } yield now.plusMillis(offset) 40 | } 41 | 42 | implicit class JodaDateAug(val dt: DateTime) { 43 | def asSql: SqlDate = new SqlDate(dt.getMillis) 44 | } 45 | 46 | implicit class JavaDateAug(val dt: Date) { 47 | def asSql: SqlDate = new SqlDate(dt.getTime) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/column/AbstractColumnTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.column 17 | 18 | import com.outworkers.morpheus.builder.SQLBuiltQuery 19 | import com.outworkers.morpheus.dsl.BaseTable 20 | import org.scalatest.{Matchers, FlatSpec} 21 | 22 | class TestColumn extends AbstractColumn[Int] { 23 | override def qb: SQLBuiltQuery = SQLBuiltQuery("integer") 24 | 25 | override def toQueryString(v: Int): String = ??? 26 | 27 | override def sqlType: String = ??? 28 | 29 | override def table: BaseTable[_, _, _] = ??? 30 | } 31 | 32 | class AbstractColumnTest extends FlatSpec with Matchers { 33 | 34 | it should "resolve column name from the name of implementing class" in { 35 | val column = new TestColumn 36 | 37 | column.name shouldEqual "TestColumn" 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/dsl/BasicTable.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.dsl 17 | 18 | import com.outworkers.morpheus.sql.DefaultRow 19 | import com.outworkers.morpheus.sql._ 20 | 21 | case class BasicRecord(name: String, count: Long) 22 | 23 | class BasicTable extends Table[BasicTable, BasicRecord] { 24 | 25 | object name extends TextColumn(this) 26 | object count extends LongColumn(this) 27 | 28 | def fromRow(row: DefaultRow): BasicRecord = { 29 | BasicRecord(name(row), count(row)) 30 | } 31 | 32 | } 33 | 34 | object BasicTable extends BasicTable 35 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/dsl/SQLBuiltQueryTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.dsl 17 | 18 | import com.outworkers.morpheus.builder.SQLBuiltQuery 19 | import com.outworkers.util.samplers._ 20 | import org.scalatest.prop.GeneratorDrivenPropertyChecks 21 | import org.scalatest.{Matchers, FlatSpec} 22 | 23 | class SQLBuiltQueryTest extends FlatSpec with Matchers { 24 | 25 | it should "serialise an append on an SQLBuiltQuery" in { 26 | val part1 = gen[ShortString].value 27 | val part2 = gen[ShortString].value 28 | 29 | val query = SQLBuiltQuery(part1).append(SQLBuiltQuery(part2)).queryString 30 | query shouldEqual s"$part1$part2" 31 | } 32 | 33 | it should "serialise a prepend on an SQLBuiltQuery" in { 34 | val part1 = gen[ShortString].value 35 | val part2 = gen[ShortString].value 36 | val query = SQLBuiltQuery(part1).prepend(SQLBuiltQuery(part2)).queryString 37 | query shouldEqual s"$part2$part1" 38 | } 39 | 40 | 41 | it should "serialise and pad an SQLBuiltQuery with a trailing space if the space is missing" in { 42 | val part1 = gen[ShortString].value 43 | val tested = part1.trim 44 | val query = SQLBuiltQuery(tested).pad.queryString 45 | query shouldEqual s"$tested " 46 | } 47 | 48 | it should "not add a trailing space if the last character of an SQLBuiltQuery is a space" in { 49 | val part1 = gen[ShortString].value 50 | val s = part1 + " " 51 | val query = SQLBuiltQuery(s).pad.queryString 52 | query shouldEqual s"$s" 53 | } 54 | 55 | it should "wrap a value in a set of parentheses" in { 56 | val part1 = gen[ShortString].value 57 | val value = gen[ShortString].value 58 | val query = SQLBuiltQuery(part1).wrap(value).queryString 59 | query shouldEqual s"$part1 ($value)" 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/dsl/TableTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.dsl 17 | 18 | import org.scalatest.{FlatSpec, Matchers} 19 | 20 | class TableTest extends FlatSpec with Matchers { 21 | 22 | it should "correctly initialise table columns via reflection and force greedy object initialisation of Table object members" in { 23 | BasicTable.columns.size shouldEqual 2 24 | } 25 | 26 | it should "correctly extract the name of a table directly from the Scala object name" in { 27 | BasicTable.tableName shouldEqual "BasicTable" 28 | } 29 | 30 | it should "correctly extract the name of the columns inside a table" in { 31 | BasicTable.count.name shouldEqual "count" 32 | BasicTable.name.name shouldEqual "name" 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/engine/query/CompileTimeRestrictionsTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.engine.query 17 | 18 | import com.outworkers.morpheus.dsl.{BasicRecord, BasicTable} 19 | import org.scalatest.{FlatSpec, Matchers} 20 | import com.outworkers.morpheus.dsl.BasicRecord 21 | import com.outworkers.morpheus.sql._ 22 | import com.outworkers.morpheus.tables.{IndexTable, KeysRecord, KeysTable} 23 | 24 | class CompileTimeRestrictionsTest extends FlatSpec with Matchers { 25 | 26 | it should " compile a SELECT query with a limit set before an orderBy clause" in { 27 | """BasicTable.select 28 | .limit(10) 29 | .orderBy(_.count asc, _.name desc) 30 | """ should compile 31 | } 32 | 33 | it should " compile a SELECT query with a limit set after an orderBy clause" in { 34 | """BasicTable.select 35 | .orderBy(_.count asc, _.name desc) 36 | .limit(10) 37 | """ should compile 38 | } 39 | 40 | it should "not compile a SELECT query with a limit set before and after an orderBy clause" in { 41 | """BasicTable.select 42 | .limit(10) 43 | .orderBy(_.count asc, _.name desc) 44 | .limit(10) 45 | """ shouldNot compile 46 | } 47 | 48 | it should "not compile a SELECT query with two limit clauses" in { 49 | BasicTable.select 50 | .limit(10) 51 | 52 | """BasicTable.select 53 | .limit(10) 54 | .limit(10) 55 | """ shouldNot compile 56 | } 57 | 58 | it should " compile a SELECT query with a an orderBy clause set after a limit clause" in { 59 | """BasicTable.select 60 | .limit(10) 61 | .orderBy(_.count asc, _.name desc) 62 | """ should compile 63 | } 64 | 65 | it should " compile a SELECT query with a an orderBy clause set before a limit clause" in { 66 | """BasicTable.select 67 | .orderBy(_.count asc, _.name desc) 68 | .limit(10) 69 | """ should compile 70 | } 71 | 72 | it should "not compile a SELECT query with a an orderBy clause set before and after a limit clause" in { 73 | """BasicTable.select 74 | .orderBy(_.count asc, _.name desc) 75 | .limit(10) 76 | """ should compile 77 | } 78 | 79 | it should "not compile a SELECT query with two orderBy clauses" in { 80 | """BasicTable.select 81 | .orderBy(_.count asc, _.name desc) 82 | .orderBy(_.count asc, _.name desc) 83 | """ shouldNot compile 84 | } 85 | 86 | it should "compile a SELECT query with a single groupBy clause" in { 87 | """BasicTable.select 88 | .groupBy(_.count, _.name) 89 | """ should compile 90 | } 91 | 92 | it should "not compile a SELECT query with two groupBy clauses" in { 93 | """BasicTable.select 94 | .groupBy(_.count, _.name) 95 | .groupBy(_.count, _.name) 96 | """ shouldNot compile 97 | } 98 | 99 | it should "compile a select query groupped before ordering" in { 100 | """BasicTable.select 101 | .groupBy(_.count, _.name) 102 | .orderBy(_.name asc) 103 | .queryString""" should compile 104 | } 105 | 106 | it should "not compile a select query ordered before grouping" in { 107 | """BasicTable.select 108 | .orderBy(_.name asc) 109 | .groupBy(_.count, _.name) 110 | .queryString""" shouldNot compile 111 | } 112 | 113 | it should "allow defining a foreignKey from one table to another" in { 114 | """object foreign extends ForeignKey[BasicTable, BasicRecord, IndexTable](BasicTable, IndexTable.id, IndexTable.value)""" should compile 115 | } 116 | 117 | it should "not allow defining a foreignKey from a table to columns in the same table" in { 118 | // this line is because InteliJ will remove the imports automatically as it's not detecting objects inside string literals. 119 | // We are also too lazy to remove the automatic import management. 120 | object foreign extends ForeignKey[BasicTable, BasicRecord, IndexTable](BasicTable, IndexTable.id, IndexTable.value) 121 | 122 | """object foreign2 extends ForeignKey[BasicTable, BasicRecord, BasicTable](BasicTable, BasicTable.name, BasicTable.count)""" shouldNot compile 123 | } 124 | 125 | it should "not allow defining a foreignKey from a table to columns belonging to multiple tables" in { 126 | """ object foreign extends ForeignKey[BasicTable, BasicRecord, IndexTable](BasicTable, IndexTable.id, KeysTable.id)""" shouldNot compile 127 | } 128 | 129 | it should "not allow defining a foreignKey from a table to column that is an index column" in { 130 | // This line is also because we are a lazy bunch. 131 | object foreign3 extends ForeignKey[BasicTable, BasicRecord, KeysTable](BasicTable, KeysTable.id) 132 | """ object foreign4 extends ForeignKey[BasicTable, BasicRecord, KeysTable](BasicTable, KeysTable.foreignKey, KeysTable.id)""" shouldNot compile 133 | } 134 | 135 | 136 | it should "not allow defining an index on a column that is a foreignKey" in { 137 | // This line is also because we are a lazy bunch. 138 | object index1 extends Index[KeysTable, KeysRecord](KeysTable.id) 139 | """object foreign3 extends Index[KeysTable, KeysRecord](KeysTable.id, KeysTable.foreignKey)""" shouldNot compile 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/engine/query/CreateQueryTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.engine.query 17 | 18 | import com.outworkers.morpheus.dsl.BasicTable 19 | import org.scalatest.{FlatSpec, Matchers} 20 | import com.outworkers.morpheus.sql._ 21 | 22 | class CreateQueryTest extends FlatSpec with Matchers { 23 | 24 | it should "serialise a simple CREATE query" in { 25 | BasicTable.create.queryString shouldEqual "CREATE TABLE `BasicTable`;" 26 | } 27 | 28 | it should "serialise a simple CREATE query with an IF NOT EXISTS clause" in { 29 | BasicTable.create.ifNotExists.queryString shouldEqual "CREATE TABLE IF NOT EXISTS `BasicTable`;" 30 | } 31 | 32 | it should "serialise a CREATE query with a TEMPORARY clause" in { 33 | BasicTable.create.temporary.queryString shouldEqual "CREATE TEMPORARY TABLE `BasicTable`;" 34 | } 35 | 36 | it should "serialise a CREATE create query with a TEMPORARY clause" in { 37 | BasicTable.create.temporary.queryString shouldEqual "CREATE TEMPORARY TABLE `BasicTable`;" 38 | } 39 | 40 | ignore should "serialise a CREATE create query with a TEMPORARY clause and an IF NOT EXISTS clause" in { 41 | BasicTable.create.temporary.ifNotExists.queryString shouldEqual "CREATE TEMPORARY TABLE IF NOT EXISTS `BasicTable`;" 42 | } 43 | 44 | it should "serialise a complete table definition when an engine is specified" in { 45 | BasicTable.create.engine(InnoDB).queryString shouldEqual "CREATE TABLE `BasicTable` (`name` TEXT, `count` LONG) ENGINE InnoDB;" 46 | } 47 | 48 | it should "serialise a complete table definition with an IF NOT EXSITS clause when an engine is specified" in { 49 | BasicTable.create.ifNotExists.engine(InnoDB) 50 | .queryString shouldEqual "CREATE TABLE IF NOT EXISTS `BasicTable` (`name` TEXT, `count` LONG) ENGINE InnoDB;" 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/engine/query/DeleteQuerySerialisationTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.engine.query 17 | 18 | import com.outworkers.morpheus.dsl._ 19 | import com.outworkers.morpheus.sql._ 20 | import org.scalatest.{FlatSpec, Matchers} 21 | 22 | class DeleteQuerySerialisationTest extends FlatSpec with Matchers { 23 | 24 | it should "serialise a simple DELETE query" in { 25 | BasicTable.delete.queryString shouldEqual "DELETE FROM `BasicTable`;" 26 | } 27 | 28 | it should "serialise a simple DELETE where query" in { 29 | BasicTable.delete 30 | .where(_.name eqs "test").queryString shouldEqual "DELETE FROM `BasicTable` WHERE name = 'test';" 31 | } 32 | 33 | it should "serialise an DELETE query with an < operator" in { 34 | BasicTable.delete 35 | .where(_.count < 5).queryString shouldEqual "DELETE FROM `BasicTable` WHERE count < 5;" 36 | } 37 | 38 | it should "serialise an DELETE query with an lt operator" in { 39 | BasicTable.delete 40 | .where(_.count lt 5) 41 | .queryString shouldEqual "DELETE FROM `BasicTable` WHERE count < 5;" 42 | } 43 | 44 | it should "serialise an DELETE query with an <= operator" in { 45 | BasicTable.delete 46 | .where(_.count <= 5) 47 | .queryString shouldEqual "DELETE FROM `BasicTable` WHERE count <= 5;" 48 | } 49 | 50 | it should "serialise an DELETE query with an lte operator" in { 51 | BasicTable.delete 52 | .where(_.count lte 5) 53 | .queryString shouldEqual "DELETE FROM `BasicTable` WHERE count <= 5;" 54 | } 55 | 56 | it should "serialise an DELETE query with a gt operator" in { 57 | BasicTable.delete 58 | .where(_.count gt 5) 59 | .queryString shouldEqual "DELETE FROM `BasicTable` WHERE count > 5;" 60 | } 61 | 62 | it should "serialise an DELETE query with a > operator" in { 63 | BasicTable.delete 64 | .where(_.count > 5) 65 | .queryString shouldEqual "DELETE FROM `BasicTable` WHERE count > 5;" 66 | } 67 | 68 | it should "serialise an DELETE query with a gte operator" in { 69 | BasicTable.delete 70 | .where(_.count gte 5).queryString shouldEqual "DELETE FROM `BasicTable` WHERE count >= 5;" 71 | } 72 | 73 | it should "serialise an DELETE query with a >= operator" in { 74 | BasicTable.delete 75 | .where(_.count >= 5).queryString shouldEqual "DELETE FROM `BasicTable` WHERE count >= 5;" 76 | } 77 | 78 | it should "not allow specifying the WHERE part before the SET in an DELETE query" in { 79 | """BasicTable.delete.where(_.name eqs 5).queryString""" shouldNot compile 80 | } 81 | 82 | it should "serialise a multiple assignments query with a single where clause" in { 83 | BasicTable.delete 84 | .where(_.name eqs "test") 85 | .queryString shouldEqual "DELETE FROM `BasicTable` WHERE name = 'test';" 86 | } 87 | 88 | it should "serialise a multiple assignments query with a multiple where clause" in { 89 | BasicTable.delete 90 | .where(_.name eqs "test") 91 | .and(_.count eqs 10) 92 | .queryString shouldEqual "DELETE FROM `BasicTable` WHERE name = 'test' AND count = 10;" 93 | } 94 | 95 | 96 | it should "serialise a simple DELETE where-and query" in { 97 | BasicTable.delete 98 | .where(_.name eqs "test") 99 | .and(_.count eqs 5).queryString shouldEqual "DELETE FROM `BasicTable` WHERE name = 'test' AND count = 5;" 100 | } 101 | 102 | it should "serialise a conditional DELETE clause with an OR operator" in { 103 | BasicTable.delete 104 | .where(_.name eqs "test") 105 | .and(t => { (t.count eqs 5) or (t.name eqs "test") }) 106 | .queryString shouldEqual "DELETE FROM `BasicTable` WHERE name = 'test' AND (count = 5 OR name = 'test');" 107 | } 108 | 109 | it should "serialise a conditional DELETE clause with an a double WHERE-OR operator" in { 110 | BasicTable.delete 111 | .where(t => { (t.count eqs 15) or (t.name eqs "test5") }) 112 | .and(t => { (t.count eqs 5) or (t.name eqs "test") }) 113 | .queryString shouldEqual "DELETE FROM `BasicTable` WHERE (count = 15 OR name = 'test5') AND (count = 5 OR name = 'test');" 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/engine/query/InFlightOperatorsTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.engine.query 17 | 18 | import com.outworkers.morpheus.dsl.BasicTable 19 | import org.scalatest.{FlatSpec, Matchers} 20 | import com.outworkers.morpheus.sql._ 21 | import com.outworkers.morpheus.tables.IndexTable 22 | 23 | class InFlightOperatorsTest extends FlatSpec with Matchers { 24 | 25 | it should "serialise an inFlight usage of a EXISTS operator" in { 26 | exists(BasicTable.select.where(_.count eqs 10)) 27 | .clause.queryString shouldEqual "EXISTS (SELECT * FROM `BasicTable` WHERE count = 10)" 28 | } 29 | 30 | it should "serialise a nested EXISTS sub-query" in { 31 | BasicTable.select 32 | .where(exists(BasicTable.select.where(_.count eqs 10))) 33 | .queryString shouldEqual "SELECT * FROM `BasicTable` WHERE EXISTS (SELECT * FROM `BasicTable` WHERE count = 10);" 34 | } 35 | 36 | 37 | it should "serialise an inFlight usage of a NOT EXISTS operator" in { 38 | notExists(BasicTable.select.where(_.count eqs 10)).clause 39 | .queryString shouldEqual "NOT EXISTS (SELECT * FROM `BasicTable` WHERE count = 10)" 40 | 41 | } 42 | 43 | it should "serialise a nested NOT EXISTS sub-query" in { 44 | BasicTable.select 45 | .where(notExists(BasicTable.select.where(_.count eqs 10))) 46 | .queryString shouldEqual "SELECT * FROM `BasicTable` WHERE NOT EXISTS (SELECT * FROM `BasicTable` WHERE count = 10);" 47 | } 48 | 49 | it should "serialise a three nested alternation of EXISTS/NOT EXISTS sub-queries" in { 50 | 51 | val qb = BasicTable.select 52 | .where(notExists(BasicTable.select.where(exists(IndexTable.select.where(_.id eqs 10))))) 53 | .queryString 54 | 55 | qb shouldEqual "SELECT * FROM `BasicTable` WHERE NOT EXISTS (SELECT * FROM `BasicTable` WHERE EXISTS (SELECT * FROM `IndexTable` WHERE id = 10));" 56 | } 57 | 58 | it should "serialise a CONCAT clause to the appropiate select query" in { 59 | rootSelectQueryToSelectQuery(BasicTable.select(_ => concat("A", "B", "C", "D"))).queryString shouldEqual "SELECT CONCAT ('A', 'B', 'C', 'D') FROM " + 60 | "`BasicTable`;" 61 | } 62 | 63 | it should "serialise an INTERVAL operator clause to a select query" in { 64 | BasicTable.select(_ => interval(5, 5, 10)).queryString shouldEqual "SELECT INTERVAL (5, 5, 10) FROM `BasicTable`;" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/engine/query/InsertQuerySerialisationTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.engine.query 17 | 18 | import com.outworkers.morpheus.sql._ 19 | import com.outworkers.morpheus.dsl._ 20 | import org.scalatest.{FlatSpec, Matchers} 21 | 22 | class InsertQuerySerialisationTest extends FlatSpec with Matchers { 23 | 24 | it should "serialise an INSERT INTO query to the correct query and convert using an implicit" in { 25 | BasicTable.insert.queryString shouldEqual "INSERT INTO `BasicTable`;" 26 | } 27 | 28 | it should "serialise an INSERT INTO query to the correct query" in { 29 | BasicTable.insert.into.queryString shouldEqual "INSERT INTO `BasicTable`;" 30 | } 31 | 32 | it should "serialise an INSERT query with a single value defined" in { 33 | BasicTable.insert 34 | .value(_.count, 5L) 35 | .queryString shouldEqual "INSERT INTO `BasicTable` (`count`) VALUES(5);" 36 | } 37 | 38 | it should "serialise an INSERT query with multiple values defined" in { 39 | BasicTable.insert 40 | .value(_.count, 5L) 41 | .value(_.name, "test") 42 | .queryString shouldEqual "INSERT INTO `BasicTable` (`count`, `name`) VALUES(5, 'test');" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/engine/query/JoinsQuerySerialisationTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.engine.query 17 | 18 | import org.scalatest.{FlatSpec, Matchers} 19 | 20 | import com.outworkers.morpheus.sql._ 21 | import com.outworkers.morpheus.tables.{IndexTable, KeysTable} 22 | 23 | class JoinsQuerySerialisationTest extends FlatSpec with Matchers { 24 | 25 | it should "serialise a simple LEFT JOIN query" in { 26 | val qb = KeysTable 27 | .select 28 | .where(_.id eqs 10) 29 | .leftJoin(IndexTable) 30 | .on(_.foreignKey eqs IndexTable.value) 31 | .queryString 32 | 33 | qb shouldEqual "SELECT * FROM `KeysTable` WHERE id = 10 LEFT JOIN `IndexTable` ON KeysTable.foreignKey = IndexTable.value;" 34 | } 35 | 36 | it should "serialise a simple INNER JOIN query" in { 37 | val qb = KeysTable 38 | .select 39 | .where(_.id eqs 10) 40 | .innerJoin(IndexTable) 41 | .on(_.foreignKey eqs IndexTable.value) 42 | .queryString 43 | 44 | qb shouldEqual "SELECT * FROM `KeysTable` WHERE id = 10 INNER JOIN `IndexTable` ON KeysTable.foreignKey = IndexTable.value;" 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/engine/query/SQLPrimitivesTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.engine.query 17 | 18 | import java.sql.Date 19 | 20 | import com.outworkers.morpheus._ 21 | import com.outworkers.morpheus.builder.DefaultQueryBuilder 22 | import com.outworkers.morpheus.helpers.TestRow 23 | import com.outworkers.morpheus.sql._ 24 | import com.outworkers.util.samplers._ 25 | import org.joda.time.DateTime 26 | import org.scalatest.prop.GeneratorDrivenPropertyChecks 27 | import org.scalatest.{FlatSpec, Matchers} 28 | 29 | import scala.util.{Success, Try} 30 | 31 | class SQLPrimitivesTest extends FlatSpec with Matchers with GeneratorDrivenPropertyChecks with CustomSamplers { 32 | 33 | "The SQL String primitive" should "always use '(apostrophes) around the serialised strings" in { 34 | val name = gen[ShortString].value 35 | val query = DataType[String].serialize(name) 36 | query shouldEqual s"'$name'" 37 | } 38 | 39 | "The SQL Long primitive" should "serialise a Long value to its string value" in { 40 | val value = gen[Long] 41 | val query = DataType[Long].serialize(value) 42 | query shouldEqual s"${value.toString}" 43 | } 44 | 45 | "The SQL Int primitive" should "serialise a Int value to its string value" in { 46 | val value = gen[Int] 47 | val query = DataType[Int].serialize(value) 48 | query shouldEqual s"${value.toString}" 49 | } 50 | 51 | it should "serialize and deserialize an Int" in { 52 | val primitive = new DefaultIntPrimitive 53 | 54 | forAll { value: Int => 55 | 56 | val row = new TestRow { 57 | override def int(name: String): Try[Int] = Success(value) 58 | } 59 | 60 | primitive.serialize(value) shouldEqual value.toString 61 | 62 | primitive.deserialize(row, "") shouldEqual Success(value) 63 | } 64 | } 65 | 66 | it should "serialize and deserialize a Double" in { 67 | val primitive = new DefaultDoublePrimitive 68 | 69 | forAll { value: Double => 70 | 71 | val row = new TestRow { 72 | override def double(name: String): Try[Double] = Success(value) 73 | } 74 | 75 | primitive.serialize(value) shouldEqual DefaultQueryBuilder.escapeValue(value.toString) 76 | 77 | primitive.deserialize(row, "") shouldEqual Success(value) 78 | } 79 | } 80 | 81 | it should "serialize and deserialize a Float" in { 82 | val primitive = new DefaultFloatPrimitive 83 | 84 | forAll { value: Float => 85 | 86 | val row = new TestRow { 87 | override def float(name: String): Try[Float] = Success(value) 88 | } 89 | 90 | primitive.serialize(value) shouldEqual DefaultQueryBuilder.escapeValue(value.toString) 91 | 92 | primitive.deserialize(row, "") shouldEqual Success(value) 93 | } 94 | } 95 | 96 | it should "serialize and deserialize a Long" in { 97 | val primitive = new DefaultLongPrimitive 98 | 99 | forAll { value: Long => 100 | 101 | val row = new TestRow { 102 | override def long(name: String): Try[Long] = Success(value) 103 | } 104 | 105 | primitive.serialize(value) shouldEqual value.toString 106 | 107 | primitive.deserialize(row, "") shouldEqual Success(value) 108 | } 109 | } 110 | 111 | it should "serialize and deserialize a Date" in { 112 | val primitive = new DefaultDatePrimitive 113 | 114 | forAll { value: Date => 115 | 116 | val row = new TestRow { 117 | override def date(name: String): Try[Date] = Success(value) 118 | } 119 | 120 | primitive.serialize(value) shouldEqual DefaultQueryBuilder.escapeValue(primitive.javaDateFormat.format(value)) 121 | 122 | primitive.deserialize(row, "") shouldEqual Success(value) 123 | } 124 | } 125 | 126 | it should "serialize and deserialize a DateTime" in { 127 | val ev = new DefaultDateTimePrimitive 128 | 129 | forAll { value: DateTime => 130 | 131 | val row = new TestRow { 132 | override def datetime(name: String): Try[DateTime] = Success(value) 133 | } 134 | 135 | ev.serialize(value) shouldEqual DefaultQueryBuilder.escapeValue(value.toString(ev.jodaDateTimeFormat)) 136 | 137 | ev.deserialize(row, "") shouldEqual Success(value) 138 | } 139 | } 140 | 141 | it should "serialize and deserialize a String" in { 142 | val primitive = new DefaultStringPrimitive 143 | 144 | forAll { value: String => 145 | val row = new TestRow { 146 | override def string(name: String): Try[String] = Success(value) 147 | } 148 | 149 | primitive.serialize(value) shouldEqual DefaultQueryBuilder.escapeValue(value) 150 | 151 | primitive.deserialize(row, "") shouldEqual Success(value) 152 | } 153 | } 154 | } 155 | 156 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/engine/query/UpdateQuerySerialisationTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.engine.query 17 | 18 | import com.outworkers.morpheus.dsl._ 19 | import com.outworkers.morpheus.sql._ 20 | import org.scalatest.{FlatSpec, Matchers} 21 | 22 | class UpdateQuerySerialisationTest extends FlatSpec with Matchers { 23 | 24 | it should "serialise a simple UPDATE query" in { 25 | BasicTable.update.queryString shouldEqual "UPDATE `BasicTable`;" 26 | } 27 | 28 | it should "serialise a simple UPDATE where query" in { 29 | BasicTable.update.set(_.count setTo 10).where(_.name eqs "test") 30 | .queryString shouldEqual "UPDATE `BasicTable` SET count = 10 WHERE name = 'test';" 31 | } 32 | 33 | it should "serialise an UPDATE query with an < operator" in { 34 | BasicTable.update.set(_.count setTo 10).where(_.count < 5).queryString shouldEqual "UPDATE `BasicTable` SET count = 10 WHERE count < 5;" 35 | } 36 | 37 | it should "serialise an UPDATE query with an lt operator" in { 38 | BasicTable.update.set(_.count setTo 10).where(_.count lt 5).queryString shouldEqual "UPDATE `BasicTable` SET count = 10 WHERE count < 5;" 39 | } 40 | 41 | it should "serialise an UPDATE query with an <= operator" in { 42 | BasicTable.update.set(_.count setTo 10).where(_.count <= 5).queryString shouldEqual "UPDATE `BasicTable` SET count = 10 WHERE count <= 5;" 43 | } 44 | 45 | it should "serialise an UPDATE query with an lte operator" in { 46 | BasicTable.update.set(_.count setTo 10).where(_.count lte 5).queryString shouldEqual "UPDATE `BasicTable` SET count = 10 WHERE count <= 5;" 47 | } 48 | 49 | it should "serialise an UPDATE query with a gt operator" in { 50 | BasicTable.update.set(_.count setTo 10).where(_.count gt 5).queryString shouldEqual "UPDATE `BasicTable` SET count = 10 WHERE count > 5;" 51 | } 52 | 53 | it should "serialise an UPDATE query with a > operator" in { 54 | BasicTable.update.set(_.count setTo 10).where(_.count > 5).queryString shouldEqual "UPDATE `BasicTable` SET count = 10 WHERE count > 5;" 55 | } 56 | 57 | it should "serialise an UPDATE query with a gte operator" in { 58 | BasicTable.update.set(_.count setTo 10).where(_.count gte 5).queryString shouldEqual "UPDATE `BasicTable` SET count = 10 WHERE count >= 5;" 59 | } 60 | 61 | it should "serialise an UPDATE query with a >= operator" in { 62 | BasicTable.update.set(_.count setTo 10).where(_.count >= 5).queryString shouldEqual "UPDATE `BasicTable` SET count = 10 WHERE count >= 5;" 63 | } 64 | 65 | it should "not allow specifying the WHERE part before the SET in an UPDATE query" in { 66 | """BasicTable.update.where(_.name eqs 5).queryString""" shouldNot compile 67 | } 68 | 69 | it should "serialise a simple assignments query" in { 70 | BasicTable.update.set(_.name setTo "test").queryString shouldEqual "UPDATE `BasicTable` SET name = 'test';" 71 | } 72 | 73 | it should "serialise a simple assignments query with a single where clause" in { 74 | BasicTable.update 75 | .set(_.name setTo "test") 76 | .where(_.count eqs 15) 77 | .queryString shouldEqual "UPDATE `BasicTable` SET name = 'test' WHERE count = 15;" 78 | } 79 | 80 | it should "serialise a multiple assignments query with a single where clause" in { 81 | BasicTable.update 82 | .set(_.name setTo "test2") 83 | .andSet(_.count setTo 15) 84 | .where(_.name eqs "test") 85 | .queryString shouldEqual "UPDATE `BasicTable` SET name = 'test2', count = 15 WHERE name = 'test';" 86 | } 87 | 88 | it should "serialise a multiple assignments query with a multiple where clause" in { 89 | BasicTable.update 90 | .set(_.name setTo "test2") 91 | .andSet(_.count setTo 15) 92 | .where(_.name eqs "test") 93 | .and(_.count eqs 10) 94 | .queryString shouldEqual "UPDATE `BasicTable` SET name = 'test2', count = 15 WHERE name = 'test' AND count = 10;" 95 | } 96 | 97 | 98 | it should "serialise a simple UPDATE where-and query" in { 99 | BasicTable.update 100 | .set(_.count setTo 10) 101 | .where(_.name eqs "test") 102 | .and(_.count eqs 5).queryString shouldEqual "UPDATE `BasicTable` SET count = 10 WHERE name = 'test' AND count = 5;" 103 | } 104 | 105 | it should "serialise a conditional UPDATE clause with an OR operator" in { 106 | BasicTable.update 107 | .set(_.count setTo 10) 108 | .where(_.name eqs "test") 109 | .and(t => { (t.count eqs 5) or (t.name eqs "test") }) 110 | .queryString shouldEqual "UPDATE `BasicTable` SET count = 10 WHERE name = 'test' AND (count = 5 OR name = 'test');" 111 | } 112 | 113 | it should "serialise a conditional UPDATE clause with an a double WHERE-OR operator" in { 114 | BasicTable.update 115 | .set(_.count setTo 10) 116 | .where(t => { (t.count eqs 15) or (t.name eqs "test5") }) 117 | .and(t => { (t.count eqs 5) or (t.name eqs "test") }) 118 | .queryString shouldEqual "UPDATE `BasicTable` SET count = 10 WHERE (count = 15 OR name = 'test5') AND (count = 5 OR name = 'test');" 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/engine/query/WhereClauseOperatorsTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.engine.query 17 | 18 | import com.outworkers.morpheus.dsl.BasicTable 19 | import org.scalatest.{FlatSpec, Matchers} 20 | import com.outworkers.morpheus.sql._ 21 | 22 | class WhereClauseOperatorsTest extends FlatSpec with Matchers { 23 | it should "serialise a SELECT clause with a BETWEEN - AND operator sequence" in { 24 | BasicTable.select.where(_.count between 5 and 10).queryString shouldEqual "SELECT * FROM `BasicTable` WHERE count BETWEEN 5 AND 10;" 25 | } 26 | 27 | it should "serialise a SELECT clause with a BETWEEN - AND operator sequence inside an OR sequence" in { 28 | BasicTable.select 29 | .where(t => { (t.count between 5 and 10) or (t.count gte 5) }) 30 | .queryString shouldEqual "SELECT * FROM `BasicTable` WHERE (count BETWEEN 5 AND 10 OR count >= 5);" 31 | } 32 | 33 | it should "serialise a SELECT clause with a NOT BETWEEN - AND operator sequence" in { 34 | BasicTable.select.where(_.count notBetween 5 and 10) 35 | .queryString shouldEqual "SELECT * FROM `BasicTable` WHERE count NOT BETWEEN 5 AND 10;" 36 | } 37 | 38 | it should "serialise a SELECT clause with a NOT BETWEEN - AND operator sequence inside an OR sequence" in { 39 | BasicTable.select 40 | .where(t => { (t.count notBetween 5 and 10) or (t.count gte 5) }) 41 | .queryString shouldEqual "SELECT * FROM `BasicTable` WHERE (count NOT BETWEEN 5 AND 10 OR count >= 5);" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/helpers/TestRow.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.helpers 17 | 18 | import java.nio.ByteBuffer 19 | import java.util.Date 20 | import java.sql.{Timestamp, Date => SqlDate} 21 | 22 | import com.outworkers.morpheus.Row 23 | 24 | import scala.util.control.NoStackTrace 25 | import scala.util.{Failure, Try} 26 | 27 | class TestRow extends Row { 28 | 29 | def failWith[T](msg: String): Failure[T] = Failure(new RuntimeException(msg) with NoStackTrace) 30 | 31 | override def bool(name: String): Try[Boolean] = failWith("Bool extraction not implemented") 32 | 33 | override def byte(name: String): Try[Byte] = failWith("Byte extraction not implemented") 34 | 35 | override def string(name: String): Try[String] = failWith("String extraction not implemented") 36 | 37 | override def byteBuffer(name: String): Try[ByteBuffer] = failWith("ByteBuffer extraction not implemented") 38 | 39 | override def int(name: String): Try[Int] = failWith("Int extraction not implemented") 40 | 41 | override def double(name: String): Try[Double] = failWith("Double extraction not implemented") 42 | 43 | override def short(name: String): Try[Short] = failWith("Short extraction not implemented") 44 | 45 | override def date(name: String): Try[Date] = failWith("Date extraction not implemented") 46 | 47 | override def sqlDate(name: String): Try[SqlDate] = failWith("Date extraction not implemented") 48 | 49 | override def float(name: String): Try[Float] = failWith("Float extraction not implemented") 50 | 51 | override def long(name: String): Try[Long] = failWith("Long extraction not implemented") 52 | 53 | override def bigDecimal(name: String): Try[BigDecimal] = failWith("BigDecimal extraction not implemented") 54 | 55 | override def timestamp(name: String): Try[Timestamp] = failWith("Timestamp extraction not implemented") 56 | } 57 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/schema/KeysSerialisationTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.schema 17 | 18 | import com.outworkers.morpheus.tables.KeysTable 19 | import org.scalatest.{FlatSpec, Matchers} 20 | 21 | class KeysSerialisationTest extends FlatSpec with Matchers { 22 | 23 | it should "serialise a PrimaryKey definition to an SQL query" in { 24 | KeysTable.id.qb.queryString shouldEqual "id INT PRIMARY KEY" 25 | } 26 | 27 | it should "serialise a PrimaryKey NotNull definition to an SQL query" in { 28 | KeysTable.notNullId.qb.queryString shouldEqual "notNullId INT PRIMARY KEY NOT NULL" 29 | } 30 | 31 | it should "serialise a PrimaryKey Autoincrement definition to an SQL query" in { 32 | KeysTable.autoincrementedId.qb.queryString shouldEqual "autoincrementedId INT PRIMARY KEY AUTO_INCREMENT" 33 | } 34 | 35 | it should "serialise a PrimaryKey NotNull AutoIncrement definition to an SQL query" in { 36 | KeysTable.indexId.qb.queryString shouldEqual "indexId INT PRIMARY KEY NOT NULL AUTO_INCREMENT" 37 | } 38 | 39 | 40 | it should "serialise a simple ForeignKey definition to an SQL query without any constraints defined by default" in { 41 | KeysTable.foreignKey.qb.queryString shouldEqual "FOREIGN KEY (IndexTable_id, IndexTable_value) REFERENCES IndexTable(id, value)" 42 | } 43 | 44 | it should "serialise a simple ForeignKey definition to an SQL query with an onUpdate constraint defined" in { 45 | KeysTable.foreignUpdateKey.qb.queryString shouldEqual "FOREIGN KEY (IndexTable_id, IndexTable_value) REFERENCES IndexTable(id, value) ON UPDATE CASCADE" 46 | } 47 | 48 | it should "serialise a simple ForeignKey definition to an SQL query with an onDelete constraint defined" in { 49 | KeysTable.foreignDeleteKey.qb.queryString shouldEqual "FOREIGN KEY (IndexTable_id, IndexTable_value) REFERENCES IndexTable(id, value) ON DELETE CASCADE" 50 | } 51 | 52 | it should "serialise a simple ForeignKey definition to an SQL query with both constraints defined" in { 53 | KeysTable.foreignFull.qb.queryString shouldEqual "FOREIGN KEY (IndexTable_id, IndexTable_value) REFERENCES IndexTable(id, " + 54 | "value) ON UPDATE CASCADE ON DELETE CASCADE" 55 | } 56 | 57 | 58 | it should "serialise a simple ForeignKey definition to an SQL query with both constraints defined as RESTRICT" in { 59 | KeysTable.foreignFullRestrict.qb.queryString shouldEqual "FOREIGN KEY (IndexTable_id, IndexTable_value) REFERENCES IndexTable(id, " + 60 | "value) ON UPDATE RESTRICT ON DELETE RESTRICT" 61 | } 62 | 63 | it should "serialise a simple ForeignKey definition to an SQL query with both constraints defined as RESTRICT and SET NULL" in { 64 | KeysTable.foreignFullRestrictSetNull.qb.queryString shouldEqual "FOREIGN KEY (IndexTable_id, IndexTable_value) REFERENCES IndexTable(id, " + 65 | "value) ON UPDATE RESTRICT ON DELETE SET NULL" 66 | } 67 | 68 | it should "serialise a UniqueKey definition to an SQL query" in { 69 | KeysTable.uniqueIndex.qb.queryString shouldEqual "uniqueIndex TEXT UNIQUE KEY" 70 | } 71 | 72 | it should "serialise a UniqueKey NotNull definition to an SQL query" in { 73 | KeysTable.uniqueIndex.qb.queryString shouldEqual "uniqueIndex TEXT UNIQUE KEY" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/schema/NumericColumnsSerialisationTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.schema 17 | 18 | import org.scalatest.{FlatSpec, Matchers} 19 | 20 | import com.outworkers.morpheus.tables.NumericsTable 21 | 22 | class NumericColumnsSerialisationTest extends FlatSpec with Matchers { 23 | 24 | it should "serialise a simple TinyIntColumn definition to an SQL query without a limit set" in { 25 | NumericsTable.tinyInt.qb.queryString shouldEqual "`tinyInt` TINYINT" 26 | } 27 | 28 | it should "serialise a limited TinyIntColumn definition to an SQL query with a limit set" in { 29 | NumericsTable.tinyIntLimited.qb.queryString shouldEqual "`tinyIntLimited` TINYINT(100)" 30 | } 31 | 32 | it should "serialise a simple SmallIntColumn definition to an SQL query without a limit set" in { 33 | NumericsTable.smallInt.qb.queryString shouldEqual "`smallInt` SMALLINT" 34 | } 35 | 36 | it should "serialise a limited SmallIntColumn definition to an SQL query with a limit set" in { 37 | NumericsTable.smallIntLimited.qb.queryString shouldEqual "`smallIntLimited` SMALLINT(100)" 38 | } 39 | 40 | it should "serialise a simple MediumIntColumn definition to an SQL query without a limit set" in { 41 | NumericsTable.mediumInt.qb.queryString shouldEqual "`mediumInt` MEDIUMINT" 42 | } 43 | 44 | it should "serialise a limited MediumIntColumn definition to an SQL query with a limit set" in { 45 | NumericsTable.mediumIntLimited.qb.queryString shouldEqual "`mediumIntLimited` MEDIUMINT(100)" 46 | } 47 | 48 | it should "serialise a simple IntColumn definition to an SQL query without a limit set" in { 49 | NumericsTable.int.qb.queryString shouldEqual "`int` INT" 50 | } 51 | 52 | it should "serialise a limited IntColumn definition to an SQL query with a limit set" in { 53 | NumericsTable.intLimited.qb.queryString shouldEqual "`intLimited` INT(100)" 54 | } 55 | 56 | it should "serialise a year column definition" in { 57 | NumericsTable.yearCol.qb.queryString shouldEqual "`yearCol` YEAR" 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/schema/StringColumnSerialisationTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.schema 17 | 18 | import org.scalatest.{Matchers, FlatSpec} 19 | 20 | import com.outworkers.morpheus.column.KnownTypeLimits 21 | import com.outworkers.morpheus.tables.StringsTable 22 | 23 | class StringColumnSerialisationTest extends FlatSpec with Matchers { 24 | 25 | it should "serialise a simple CHAR definition to an SQL query without a limit set" in { 26 | StringsTable.charColumn.qb.queryString shouldEqual s"`charColumn` CHAR(${KnownTypeLimits.charLimit})" 27 | } 28 | 29 | it should "serialise a simple CHAR definition to an SQL query with a limit set" in { 30 | StringsTable.charLimited.qb.queryString shouldEqual s"`charLimited` CHAR(100)" 31 | } 32 | 33 | it should "serialise a simple VARCHAR definition to an SQL query without a limit set" in { 34 | StringsTable.varChar.qb.queryString shouldEqual s"`varChar` VARCHAR(${KnownTypeLimits.varcharLimit})" 35 | } 36 | 37 | it should "serialise a simple VARCHAR definition to an SQL query with a limit set" in { 38 | StringsTable.varCharLimited.qb.queryString shouldEqual "`varCharLimited` VARCHAR(100)" 39 | } 40 | 41 | it should "serialise a TINYTEXT column definition to the correct SQL type" in { 42 | StringsTable.tinyText.qb.queryString shouldEqual "`tinyText` TINYTEXT" 43 | } 44 | 45 | it should "serialise a MEDIUMTEXT column definition to the correct SQL type" in { 46 | StringsTable.mediumText.qb.queryString shouldEqual "`mediumText` MEDIUMTEXT" 47 | } 48 | 49 | it should "serialise a LONGTEXT column definition to the correct SQL type" in { 50 | StringsTable.longText.qb.queryString shouldEqual "`longText` LONGTEXT" 51 | } 52 | 53 | it should "serialise a TEXT column definition to the correct SQL type" in { 54 | StringsTable.textColumn.qb.queryString shouldEqual "`textColumn` TEXT" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/schema/ZeroFillColumnsSerialisationTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.schema 17 | 18 | import org.scalatest.{Matchers, FlatSpec} 19 | 20 | import com.outworkers.morpheus.tables.ZeroFillTable 21 | 22 | class ZeroFillColumnsSerialisationTest extends FlatSpec with Matchers { 23 | 24 | it should "serialise a ZEROFILL UNSIGNED NOT NULL column" in { 25 | ZeroFillTable.tinyInt.qb.queryString shouldEqual "tinyInt TINYINT ZEROFILL UNSIGNED NOT NULL" 26 | } 27 | 28 | it should "serialise a ZEROFILL NOT NULL column" in { 29 | ZeroFillTable.tinyIntLimited.qb.queryString shouldEqual "tinyIntLimited TINYINT(5) ZEROFILL NOT NULL" 30 | } 31 | 32 | it should "serialise a ZEROFILL UNSIGNED column" in { 33 | ZeroFillTable.smallInt.qb.queryString shouldEqual "smallInt SMALLINT ZEROFILL UNSIGNED" 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /morpheus-dsl/src/test/scala/com/outworkers/morpheus/tables/IndexTable.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.tables 17 | 18 | import com.outworkers.morpheus.keys.Unsigned 19 | import com.outworkers.morpheus.sql._ 20 | 21 | case class IndexedRecord(id: Int, value: Long) 22 | 23 | sealed class IndexTable extends Table[IndexTable, IndexedRecord] { 24 | 25 | object id extends SmallIntColumn(this) with PrimaryKey with NotNull with Autoincrement 26 | 27 | object value extends LongColumn(this) 28 | 29 | object index extends Index(id, value) 30 | 31 | def fromRow(row: Row): IndexedRecord = { 32 | IndexedRecord( 33 | id(row), 34 | value(row) 35 | ) 36 | } 37 | } 38 | 39 | object IndexTable extends IndexTable 40 | 41 | case class KeysRecord(id: Int) 42 | 43 | sealed class KeysTable extends Table[KeysTable, KeysRecord] { 44 | 45 | object id extends IntColumn(this) with PrimaryKey 46 | 47 | object notNullId extends IntColumn(this) with PrimaryKey with NotNull 48 | 49 | object autoincrementedId extends IntColumn(this) with PrimaryKey with Autoincrement 50 | 51 | object indexId extends IntColumn(this) with PrimaryKey with NotNull with Autoincrement 52 | 53 | object foreignKey extends ForeignKey[KeysTable, KeysRecord, IndexTable](this, IndexTable.id, IndexTable.value) 54 | 55 | object foreignUpdateKey extends ForeignKey[KeysTable, KeysRecord, IndexTable](this, IndexTable.id, IndexTable.value) { 56 | override def onUpdate = Cascade 57 | } 58 | 59 | object foreignDeleteKey extends ForeignKey[KeysTable, KeysRecord, IndexTable](this, IndexTable.id, IndexTable.value) { 60 | override def onDelete = Cascade 61 | } 62 | 63 | object foreignFull extends ForeignKey[KeysTable, KeysRecord, IndexTable](this, IndexTable.id, IndexTable.value) { 64 | override def onUpdate = Cascade 65 | override def onDelete = Cascade 66 | } 67 | 68 | object foreignFullRestrict extends ForeignKey[KeysTable, KeysRecord, IndexTable](this, IndexTable.id, IndexTable.value) { 69 | override def onUpdate = Restrict 70 | override def onDelete = Restrict 71 | } 72 | 73 | object foreignFullRestrictSetNull extends ForeignKey[KeysTable, KeysRecord, IndexTable](this, IndexTable.id, IndexTable.value) { 74 | override def onUpdate = Restrict 75 | override def onDelete = SetNull 76 | } 77 | 78 | object uniqueIndex extends TextColumn(this) with UniqueKey 79 | object uniqueIndexNotNull extends TextColumn(this) with UniqueKey with NotNull 80 | 81 | 82 | 83 | /** 84 | * The most notable and honorable of functions in this file, this is what allows our DSL to provide type-safety. 85 | * It works by requiring a user to define a type-safe mapping between a buffered Result and the above refined Record. 86 | * 87 | * Objects delimiting pre-defined columns also have a pre-defined "apply" method, allowing the user to simply autofill the type-safe mapping by using 88 | * pre-existing definitions. 89 | * 90 | * @param row The row incoming as a result from a MySQL query. 91 | * @return A Record instance. 92 | */ 93 | override def fromRow(row: Row): KeysRecord = KeysRecord(id(row)) 94 | } 95 | 96 | object KeysTable extends KeysTable 97 | 98 | 99 | class NumericsTable extends Table[NumericsTable, Int] { 100 | 101 | object tinyInt extends TinyIntColumn(this) 102 | object tinyIntLimited extends TinyIntColumn(this, 100) 103 | 104 | object smallInt extends SmallIntColumn(this) 105 | object smallIntLimited extends SmallIntColumn(this, 100) 106 | 107 | object mediumInt extends MediumIntColumn(this) 108 | object mediumIntLimited extends MediumIntColumn(this, 100) 109 | 110 | object int extends IntColumn(this) 111 | object intLimited extends IntColumn(this, 100) 112 | object yearCol extends YearColumn(this) 113 | 114 | def fromRow(row: Row): Int = int(row) 115 | } 116 | 117 | object NumericsTable extends NumericsTable 118 | 119 | 120 | class StringsTable extends Table[StringsTable, String] { 121 | 122 | object charColumn extends CharColumn(this) 123 | object charLimited extends CharColumn(this, 100) 124 | 125 | object varChar extends VarcharColumn(this) 126 | object varCharLimited extends VarcharColumn(this, 100) 127 | 128 | object tinyText extends TinyTextColumn(this) 129 | object mediumText extends MediumTextColumn(this) 130 | object longText extends LongTextColumn(this) 131 | object textColumn extends TextColumn(this) 132 | 133 | object blobColumn extends BlobColumn(this) 134 | object tinyBlog extends TinyBlobColumn(this) 135 | object mediumBlob extends MediumBlobColumn(this) 136 | object largeBlob extends LongBlobColumn(this) 137 | 138 | def fromRow(row: Row): String = textColumn(row) 139 | } 140 | 141 | object StringsTable extends StringsTable 142 | 143 | 144 | class ZeroFillTable extends Table[ZeroFillTable, Int] { 145 | 146 | object tinyInt extends TinyIntColumn(this) with Zerofill with Unsigned[Int] with NotNull 147 | object tinyIntLimited extends TinyIntColumn(this, 5) with Zerofill with NotNull 148 | 149 | object smallInt extends SmallIntColumn(this) with Zerofill with Unsigned[Int] 150 | object smallIntLimited extends SmallIntColumn(this, 5) 151 | 152 | 153 | def fromRow(row: Row): Int = tinyInt(row) 154 | } 155 | 156 | object ZeroFillTable extends ZeroFillTable 157 | -------------------------------------------------------------------------------- /morpheus-mysql/src/main/scala/com/outworkers/morpheus/mysql/Columns.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.mysql 17 | 18 | import com.outworkers.morpheus.DataType 19 | import com.outworkers.morpheus.builder.DefaultSQLDataTypes 20 | import com.outworkers.morpheus.column._ 21 | import com.outworkers.morpheus.dsl.BaseTable 22 | import shapeless.{<:!<, =:!=} 23 | 24 | trait Keys { 25 | 26 | abstract class ForeignKey[ 27 | T <: BaseTable[T, R, Row], 28 | R, 29 | T1 <: BaseTable[T1, _, Row] 30 | ](origin: T, columns: IndexColumn#NonIndexColumn[T1]*)( 31 | implicit ev: T =:!= T1, 32 | ev2: IndexColumn#NonIndexColumn[T1] <:!< IndexColumn 33 | ) extends AbstractForeignKey[T, R, Row, T1](origin, columns: _*) 34 | 35 | class Index[T <: BaseTable[T, R, Row], R]( 36 | columns: IndexColumn#NonIndexColumn[_]* 37 | )(implicit ev: IndexColumn#NonIndexColumn[_] <:!< IndexColumn) extends AbstractIndex[T, R, Row](columns: _*) 38 | 39 | } 40 | 41 | trait PrimitiveColumns { 42 | 43 | class DoubleColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)( 44 | implicit ev: DataType[Double] 45 | ) extends NumericColumn[T, R, Row, Double](t, limit) { 46 | override protected[this] def numericType: String = DataTypes.Real.double 47 | } 48 | 49 | class RealColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)( 50 | implicit ev: DataType[Double] 51 | ) extends NumericColumn[T, R, Row, Double](t, limit) { 52 | override protected[this] def numericType: String = DataTypes.Real.double 53 | } 54 | 55 | class FloatColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)( 56 | implicit ev: DataType[Float] 57 | ) extends NumericColumn[T, R, Row, Float](t, limit) { 58 | override protected[this] def numericType: String = DataTypes.Real.float 59 | } 60 | 61 | class LongColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)( 62 | implicit ev: DataType[Long] 63 | ) extends NumericColumn[T, R, Row, Long](t, limit) { 64 | override protected[this] def numericType: String = DefaultSQLDataTypes.long 65 | } 66 | 67 | class IntColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)(implicit ev: DataType[Int]) 68 | extends AbstractIntColumn[T, R, Row](t, limit) 69 | 70 | class SmallIntColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)(implicit ev: DataType[Int]) 71 | extends AbstractSmallIntColumn[T, R, Row](t, limit) 72 | 73 | class TinyIntColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)(implicit ev: DataType[Int]) 74 | extends AbstractTinyIntColumn[T, R, Row](t, limit) 75 | 76 | class MediumIntColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)(implicit ev: DataType[Int]) 77 | extends AbstractMediumIntColumn[T, R, Row](t, limit) 78 | 79 | class DateColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)( 80 | implicit ev: DataType[java.util.Date] 81 | ) extends AbstractDateColumn[T, R, Row](t) 82 | 83 | class DateTimeColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)( 84 | implicit ev: DataType[org.joda.time.DateTime] 85 | ) extends AbstractDateTimeColumn[T, R, Row](t) 86 | 87 | } 88 | 89 | trait Columns { 90 | 91 | class BlobColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)(implicit ev: DataType[String]) 92 | extends AbstractBlobColumn[T, R, Row](t, limit) 93 | 94 | class TextColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)(implicit ev: DataType[String]) 95 | extends AbstractTextColumn[T, R, Row](t, limit) 96 | 97 | class LongTextColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)(implicit ev: DataType[String]) 98 | extends AbstractLongTextColumn[T, R, Row](t, limit) 99 | 100 | class LongBlobColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)(implicit ev: DataType[String]) 101 | extends AbstractLongBlobColumn[T, R, Row](t, limit) 102 | 103 | class MediumBlobColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)(implicit ev: DataType[String]) 104 | extends AbstractMediumBlobColumn[T, R, Row](t, limit) 105 | 106 | class MediumTextColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)(implicit ev: DataType[String]) 107 | extends AbstractMediumTextColumn[T, R, Row](t, limit) 108 | 109 | class TinyBlobColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)(implicit ev: DataType[String]) 110 | extends AbstractTinyBlobColumn[T, R, Row](t, limit) 111 | 112 | class TinyTextColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)(implicit ev: DataType[String]) 113 | extends AbstractTinyTextColumn[T, R, Row](t, limit) 114 | 115 | class VarcharColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)(implicit ev: DataType[String]) 116 | extends AbstractVarcharColumn[T, R, Row](t, limit) 117 | 118 | class CharColumn[T <: BaseTable[T, R, Row], R](t: BaseTable[T, R, Row], limit: Int = 0)(implicit ev: DataType[String]) 119 | extends AbstractCharColumn[T, R, Row](t, limit) 120 | 121 | class EnumColumn[T <: BaseTable[T, R, Row], R, EnumType <: Enumeration]( 122 | t: BaseTable[T, R, Row], 123 | enum: EnumType 124 | )(implicit ev: DataType[String]) 125 | extends AbstractEnumColumn[T, R, Row, EnumType](t, enum) { 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /morpheus-mysql/src/main/scala/com/outworkers/morpheus/mysql/DataTypes.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.mysql 17 | 18 | import com.outworkers.morpheus._ 19 | 20 | object DataTypes { 21 | object Real { 22 | val float = "FLOAT" 23 | val double = "DOUBLE" 24 | val decimal = "REAL" 25 | } 26 | } 27 | 28 | trait DataTypes { 29 | 30 | implicit object IntPrimitive extends DefaultIntPrimitive 31 | 32 | implicit object FloatPrimitive extends DefaultFloatPrimitive 33 | 34 | implicit object DoublePrimitive extends DefaultDoublePrimitive 35 | 36 | implicit object LongPrimitive extends DefaultLongPrimitive 37 | 38 | implicit object DatePrimitive extends DefaultDatePrimitive 39 | 40 | implicit object SqlDatePrimitive extends DefaultSqlDatePrimitive 41 | 42 | implicit object DateTimePrimitive extends DefaultDateTimePrimitive 43 | 44 | implicit object TimeStampPrimitive extends DefaultTimestampPrimitive 45 | 46 | implicit object ShortPrimitive extends DefaultShortPrimitive 47 | 48 | implicit object StringPrimitive extends DefaultStringPrimitive 49 | } 50 | -------------------------------------------------------------------------------- /morpheus-mysql/src/main/scala/com/outworkers/morpheus/mysql/Implicits.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com 17 | package outworkers 18 | package morpheus 19 | package mysql 20 | 21 | import com.outworkers.morpheus.mysql.query.{ RootInsertQuery, RootUpdateQuery, UpdateQuery} 22 | import com.outworkers.morpheus.engine.query.{AssignBind, AssignUnchainned} 23 | import com.outworkers.morpheus.builder.SQLBuiltQuery 24 | import com.outworkers.morpheus.column.{AbstractColumn, AbstractModifyColumn, Column, SelectColumn} 25 | import com.outworkers.morpheus.dsl.BaseTable 26 | import com.outworkers.morpheus.engine.query._ 27 | import com.outworkers.morpheus.{Row => MorpheusRow} 28 | import shapeless.{HList, HNil} 29 | 30 | import scala.util.Try 31 | 32 | trait Implicits extends DefaultSQLEngines { 33 | 34 | implicit class SelectColumnRequired[ 35 | Owner <: BaseTable[Owner, Record, TableRow], 36 | Record, TableRow <: MorpheusRow, T 37 | ](col: Column[Owner, Record, TableRow, T]) extends SelectColumn[T](SQLBuiltQuery(col.name)) { 38 | def apply(r: morpheus.Row): T = col.apply(r) 39 | } 40 | 41 | implicit class ModifyColumn[RR : DataType](col: AbstractColumn[RR]) extends AbstractModifyColumn[RR](col) 42 | implicit class OrderingColumn[RR : DataType](col: AbstractColumn[RR]) extends AbstractOrderingColumn[RR](col) 43 | 44 | implicit def selectOperatorClauseToSelectColumn[T]( 45 | clause: SelectOperatorClause[T] 46 | ): SelectColumn[T] = new SelectColumn[T](clause.qb) { 47 | def apply(row: MorpheusRow): T = clause.fromRow(row) 48 | } 49 | 50 | /** 51 | * This defines an implicit conversion from a RootInsertQuery to an InsertQuery, 52 | * making the INSERT syntax block invisible to the end user. 53 | * This is used to automatically "exit" the INSERT syntax block with the default "INSERT INTO" option, 54 | * while picking no other SQL options such as IGNORE or 55 | * LOW_PRIORITY. 56 | * 57 | * This is making the following queries equivalent: 58 | * - Table.insert.into.queryString = "INSERT INTO table" 59 | * - Table.insert = "INSERT INTO table" 60 | * @param root The RootSelectQuery to convert. 61 | * @tparam T The table owning the record. 62 | * @tparam R The record type. 63 | * @return An executable SelectQuery. 64 | */ 65 | implicit def rootInsertQueryToQuery[T <: BaseTable[T, _, mysql.Row], R]( 66 | root: RootInsertQuery[T, R] 67 | ): mysql.query.InsertQuery[T, R, Ungroupped, Unordered, Unlimited, Unchainned, AssignUnchainned, HNil] = { 68 | new mysql.query.InsertQuery( 69 | root.table, 70 | root.st.into, 71 | root.rowFunc 72 | ) 73 | } 74 | 75 | /** 76 | * This defines an implicit conversion from a RootUpdateQuery to an UpdateQuery, 77 | * making the UPDATE syntax block invisible to the end user. 78 | * Much like a decision block, a UpdateSyntaxBlock needs a decision branch to follow, may that be nothing, LOW_PRIORITY or IGNORE. 79 | * 80 | * The one catch is that this form of "exit" from an un-executable RootUpdateQuery will directly translate the query to an "UPDATE tableName" 81 | * query, meaning no UPDATE operators will be used in the default serialisation. 82 | * 83 | * The simple assumption made here is that since the user didn't use any other provided method, 84 | * such as "lowPriority" or "ignore" the desired behaviour is 85 | * a full select. 86 | * 87 | * @param root The RootSelectQuery to convert. 88 | * @tparam T The table owning the record. 89 | * @tparam R The record type. 90 | * @return An executable SelectQuery. 91 | */ 92 | implicit def defaultUpdateQueryToUpdateQuery[ 93 | T <: BaseTable[T, R, Row], 94 | R 95 | ](root: RootUpdateQuery[T, R]): UpdateQuery[T, R, 96 | Ungroupped, Unordered, Unlimited, Unchainned, AssignUnchainned, HNil] = { 97 | new UpdateQuery( 98 | root.table, 99 | root.st.all, 100 | root.rowFunc 101 | ) 102 | } 103 | 104 | implicit def updateQueryToAssignmentsQuery[T <: BaseTable[T, R, Row], 105 | R, 106 | Group <: GroupBind, 107 | Order <: OrderBind, 108 | Limit <: LimitBind, 109 | Chain <: ChainBind, 110 | AssignChain <: AssignBind, 111 | Status <: HList 112 | ]( 113 | query: UpdateQuery[T, R, Group, Order, Limit, Chain, AssignChain, Status] 114 | ): UpdateQuery[T, R, Group, Order, Limit, Chain, AssignChain, Status] = { 115 | new UpdateQuery[T, R, Group, Order, Limit, Chain, AssignChain, Status](query.table, query.query, query.rowFunc) 116 | } 117 | 118 | def enumPrimitive[T <: Enumeration](enum: T)(implicit ev: DataType[String]): DataType[T#Value] = { 119 | new DataType[T#Value] { 120 | override val sqlType: String = ev.sqlType 121 | 122 | override def serialize(value: T#Value): String = ev.serialize(value.toString) 123 | 124 | override def deserialize(row: MorpheusRow, name: String): Try[T#Value] = { 125 | ev.deserialize(row, name) map (x => enum.withName(x)) 126 | } 127 | } 128 | } 129 | 130 | 131 | } 132 | -------------------------------------------------------------------------------- /morpheus-mysql/src/main/scala/com/outworkers/morpheus/mysql/Table.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers 17 | package morpheus 18 | package mysql 19 | 20 | import com.outworkers.morpheus.dsl.{BaseTable, SelectTable} 21 | import com.outworkers.morpheus.mysql.query._ 22 | 23 | abstract class Table[Owner <: BaseTable[Owner, Record, mysql.Row], Record] 24 | extends BaseTable[Owner, Record, Row] with SelectTable[Owner, Record, Row, RootSelectQuery, SelectSyntaxBlock] { 25 | 26 | val queryBuilder = QueryBuilder 27 | 28 | val syntax = Syntax 29 | 30 | protected[this] def createRootSelect[A <: BaseTable[A, _, Row], B]( 31 | table: A, 32 | block: SelectSyntaxBlock, 33 | rowFunc: Row => B 34 | ): RootSelectQuery[A, B] = { 35 | new RootSelectQuery[A, B](table, block, rowFunc) 36 | } 37 | 38 | protected[this] def selectBlock(query: String, tableName: String, cols: List[String] = List("*")): SelectSyntaxBlock = { 39 | new SelectSyntaxBlock(query, tableName, cols) 40 | } 41 | 42 | def update: RootUpdateQuery[Owner, Record] = new RootUpdateQuery( 43 | this.asInstanceOf[Owner], 44 | UpdateSyntaxBlock(syntax.update, tableName), 45 | fromRow 46 | ) 47 | 48 | def delete: RootDeleteQuery[Owner, Record] = new RootDeleteQuery( 49 | this.asInstanceOf[Owner], 50 | DeleteSyntaxBlock(syntax.delete, tableName), 51 | fromRow 52 | ) 53 | 54 | def insert: RootInsertQuery[Owner, Record] = new RootInsertQuery( 55 | this.asInstanceOf[Owner], 56 | new InsertSyntaxBlock(syntax.insert, tableName), 57 | fromRow 58 | ) 59 | 60 | def create: RootCreateQuery[Owner, Record] = new RootCreateQuery( 61 | this.asInstanceOf[Owner], 62 | new CreateSyntaxBlock(syntax.create, tableName), 63 | fromRow 64 | ) 65 | 66 | } 67 | -------------------------------------------------------------------------------- /morpheus-mysql/src/main/scala/com/outworkers/morpheus/mysql/dsl/package.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus 17 | package mysql 18 | 19 | import com.outworkers.morpheus.column.{AbstractColumn, DefaultForeignKeyConstraints} 20 | import com.outworkers.morpheus.dsl.DefaultImportsDefinition 21 | import com.outworkers.morpheus.engine.query._ 22 | import com.outworkers.morpheus.mysql.query.{SelectQuery, RootSelectQuery} 23 | import com.outworkers.morpheus.operators.MySQLOperatorSet 24 | import shapeless.HNil 25 | 26 | import scala.util.Try 27 | 28 | package object dsl extends DefaultImportsDefinition 29 | with Implicits 30 | with DataTypes 31 | with MySQLOperatorSet 32 | with Columns 33 | with Keys 34 | with PrimitiveColumns 35 | with DefaultForeignKeyConstraints { 36 | 37 | override implicit def columnToQueryColumn[T : DataType](col: AbstractColumn[T]): QueryColumn[T] = new QueryColumn[T](col) 38 | 39 | implicit def rootSelectQueryToQuery[T <: Table[T, _], R]( 40 | root: RootSelectQuery[T, R] 41 | ): SelectQuery[T, R, Ungroupped, Unordered, Unlimited, Unchainned, AssignUnchainned, HNil] = { 42 | new SelectQuery( 43 | root.table, 44 | root.st.*, 45 | root.rowFunc 46 | ) 47 | } 48 | 49 | type Row = mysql.Row 50 | type Result = mysql.Result 51 | 52 | type SQLTable[Owner <: BaseTable[Owner, Record, Row], Record] = mysql.Table[Owner, Record] 53 | 54 | type Table[Owner <: BaseTable[Owner, Record, Row], Record] = mysql.Table[Owner, Record] 55 | 56 | def enumToQueryConditionPrimitive[T <: Enumeration](enum: T)(implicit ev: DataType[String]): DataType[T#Value] = { 57 | new DataType[T#Value] { 58 | 59 | override def sqlType: String = ev.sqlType 60 | 61 | override def deserialize(row: com.outworkers.morpheus.Row, name: String): Try[T#Value] = { 62 | row.string(name) map { s => enum.withName(s) } 63 | } 64 | 65 | override def serialize(value: T#Value): String = ev.serialize(value.toString) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /morpheus-mysql/src/main/scala/com/outworkers/morpheus/mysql/query/DeleteQuery.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.mysql.query 17 | 18 | import com.outworkers.morpheus.mysql.{Row, Syntax} 19 | import com.outworkers.morpheus.engine 20 | import com.outworkers.morpheus.engine.query.{AssignBind, AssignUnchainned} 21 | import com.outworkers.morpheus.builder.{DefaultSQLSyntax, SQLBuiltQuery} 22 | import com.outworkers.morpheus.dsl.BaseTable 23 | import com.outworkers.morpheus.engine.query._ 24 | import shapeless.{HList, HNil} 25 | 26 | case class DeleteSyntaxBlock(query: String, tableName: String) extends engine.query.RootDeleteSyntaxBlock(query, tableName) { 27 | 28 | override val syntax = Syntax 29 | 30 | private[this] def deleteOption(option: String, table: String): SQLBuiltQuery = { 31 | qb.pad.append(option) 32 | .forcePad.append(DefaultSQLSyntax.from) 33 | .forcePad.append(table) 34 | } 35 | 36 | 37 | def lowPriority: SQLBuiltQuery = { 38 | deleteOption(syntax.Priorities.lowPriority, tableName) 39 | } 40 | 41 | def ignore: SQLBuiltQuery = { 42 | deleteOption(syntax.DeleteOptions.ignore, tableName) 43 | } 44 | 45 | def quick: SQLBuiltQuery = { 46 | deleteOption(syntax.DeleteOptions.quick, tableName) 47 | } 48 | } 49 | 50 | private[morpheus] class RootDeleteQuery[T <: BaseTable[T, _, Row], R](table: T, st: DeleteSyntaxBlock, rowFunc: Row => R) 51 | extends engine.query.RootDeleteQuery[T, R, Row](table, st, rowFunc) { 52 | 53 | def lowPriority: DeleteQuery[T, R, Ungroupped, Unordered, Unlimited, Unchainned, AssignUnchainned, HNil] = { 54 | new DeleteQuery(table, st.lowPriority, rowFunc) 55 | } 56 | 57 | def ignore: DeleteQuery[T, R, Ungroupped, Unordered, Unlimited, Unchainned, AssignUnchainned, HNil] = { 58 | new DeleteQuery(table, st.ignore, rowFunc) 59 | } 60 | } 61 | 62 | class DeleteQuery[T <: BaseTable[T, _, Row], 63 | R, 64 | Group <: GroupBind, 65 | Order <: OrderBind, 66 | Limit <: LimitBind, 67 | Chain <: ChainBind, 68 | AssignChain <: AssignBind, 69 | Status <: HList 70 | ](table: T, query: SQLBuiltQuery, rowFunc: Row => R) extends engine.query.DeleteQuery[T, R, Row, Group, Order, Limit, Chain, AssignChain, Status](table, query, 71 | rowFunc) { 72 | 73 | } 74 | -------------------------------------------------------------------------------- /morpheus-mysql/src/main/scala/com/outworkers/morpheus/mysql/query/RootCreateQuery.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.mysql.query 17 | 18 | import com.outworkers.morpheus.mysql.{Row, Syntax} 19 | import com.outworkers.morpheus.builder.AbstractSQLSyntax 20 | import com.outworkers.morpheus.dsl.BaseTable 21 | import com.outworkers.morpheus.engine 22 | 23 | 24 | class CreateSyntaxBlock(query: String, tableName: String) extends engine.query.RootCreateSyntaxBlock(query, tableName) { 25 | override def syntax: AbstractSQLSyntax = Syntax 26 | } 27 | 28 | class RootCreateQuery[T <: BaseTable[T, _, Row], R]( 29 | table: T, 30 | st: CreateSyntaxBlock, 31 | rowFunc: Row => R 32 | ) extends engine.query.RootCreateQuery[T, R, Row](table, st, rowFunc) 33 | -------------------------------------------------------------------------------- /morpheus-mysql/src/main/scala/com/outworkers/morpheus/mysql/query/RootInsertQuery.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.mysql.query 17 | 18 | import com.outworkers.morpheus.DataType 19 | import com.outworkers.morpheus.mysql.{Row, Syntax} 20 | import com.outworkers.morpheus.builder.SQLBuiltQuery 21 | import com.outworkers.morpheus.column.AbstractColumn 22 | import com.outworkers.morpheus.dsl.BaseTable 23 | import com.outworkers.morpheus.engine 24 | import com.outworkers.morpheus.engine.query._ 25 | import com.outworkers.morpheus.engine.query.parts.{ColumnsPart, Defaults, LightweightPart, ValuePart} 26 | import shapeless.{HList, HNil} 27 | 28 | import scala.annotation.implicitNotFound 29 | 30 | private[morpheus] class InsertSyntaxBlock( 31 | query: String, 32 | tableName: String 33 | ) extends engine.query.RootInsertSyntaxBlock(query, tableName) { 34 | override val syntax = Syntax 35 | 36 | private[this] def insertOption(option: String, table: String): SQLBuiltQuery = { 37 | qb.pad.append(option) 38 | .forcePad.append(syntax.into) 39 | .forcePad.append(table) 40 | } 41 | 42 | 43 | def delayed: SQLBuiltQuery = { 44 | insertOption(syntax.InsertOptions.delayed, tableName) 45 | } 46 | 47 | def lowPriority: SQLBuiltQuery = { 48 | insertOption(syntax.Priorities.lowPriority, tableName) 49 | } 50 | 51 | def highPriority: SQLBuiltQuery = { 52 | insertOption(syntax.Priorities.highPriority, tableName) 53 | } 54 | 55 | def ignore: SQLBuiltQuery = { 56 | insertOption(syntax.InsertOptions.ignore, tableName) 57 | } 58 | } 59 | 60 | 61 | class RootInsertQuery[T <: BaseTable[T, _, Row], R](table: T, st: InsertSyntaxBlock, rowFunc: Row => R) 62 | extends engine.query.RootInsertQuery[T, R, Row](table, st, rowFunc) { 63 | 64 | def delayed: InsertQuery.Default[T, R] = { 65 | new InsertQuery(table, st.delayed, rowFunc) 66 | } 67 | 68 | def lowPriority: InsertQuery.Default[T, R] = { 69 | new InsertQuery(table, st.lowPriority, rowFunc) 70 | } 71 | 72 | def highPriority: InsertQuery.Default[T, R] = { 73 | new InsertQuery(table, st.highPriority, rowFunc) 74 | } 75 | 76 | def ignore: InsertQuery.Default[T, R] = { 77 | new InsertQuery(table, st.ignore, rowFunc) 78 | } 79 | 80 | } 81 | 82 | class InsertQuery[T <: BaseTable[T, _, Row], 83 | R, 84 | Group <: GroupBind, 85 | Order <: OrderBind, 86 | Limit <: LimitBind, 87 | Chain <: ChainBind, 88 | AssignChain <: AssignBind, 89 | Status <: HList 90 | ](table: T, 91 | override val init: SQLBuiltQuery, 92 | rowFunc: Row => R, 93 | columnsPart: ColumnsPart = Defaults.EmptyColumnsPart, 94 | valuePart: ValuePart = Defaults.EmptyValuePart, 95 | lightweightPart: LightweightPart = Defaults.EmptyLightweightPart 96 | ) extends engine.query.InsertQuery[T, R, Row, Group, Order, Limit, Chain, AssignChain, Status](table: T, init, rowFunc) { 97 | 98 | override def query: SQLBuiltQuery = (columnsPart merge valuePart merge lightweightPart) build init 99 | 100 | override protected[this] def create[ 101 | G <: GroupBind, 102 | O <: OrderBind, 103 | L <: LimitBind, 104 | S <: ChainBind, 105 | C <: AssignBind, 106 | P <: HList 107 | ](t: T, q: SQLBuiltQuery, r: Row => R): QueryType[G, O, L, S, C, P] = { 108 | new InsertQuery(t, q, r, columnsPart, valuePart, lightweightPart) 109 | } 110 | 111 | /** 112 | * At this point you may be reading and thinking "WTF", but fear not, it all makes sense. 113 | * Every call to a "value method" will generate a new Insert Query, 114 | * but the list of statements in the new query will include a new (String, String) pair, 115 | * where the first part is the column name and the second one is the 116 | * serialised value 117 | * 118 | * This is a very simple accumulator that will eventually allow calling the "insert" method on a queryBuilder to produce the final 119 | * serialisation result, a hopefully valid MySQL insert query. 120 | * 121 | * @param insertion The insert condition is a pair of a column with the value to use for it. 122 | * It looks like this: value(_.someColumn, someValue), where the assignment is of course type safe. 123 | * @param obj The object is the value to use for the column. 124 | * @tparam RR The SQL primitive or rather it's Scala correspondent to use at this time. 125 | * @return A new InsertQuery, where the list of statements in the Insert has been chained and updated for serialisation. 126 | */ 127 | @implicitNotFound(msg = "To use the value method this query needs to be an insert query and the query needs to be unterminated. You probably have more " + 128 | "value calls than columns in your table, which would result in an invalid MySQL query.") 129 | override def value[RR : DataType]( 130 | insertion: T => AbstractColumn[RR], obj: RR 131 | ): InsertQuery[T, R, Group, Order, Limit, Chain, AssignChain, Status] = { 132 | new InsertQuery[T, R, Group, Order, Limit, Chain, AssignChain, Status]( 133 | table, 134 | init, 135 | fromRow, 136 | columnsPart append SQLBuiltQuery(insertion(table).name), 137 | valuePart append SQLBuiltQuery(implicitly[DataType[RR]].serialize(obj)), 138 | lightweightPart 139 | ) 140 | } 141 | } 142 | 143 | object InsertQuery { 144 | type Default[T <: BaseTable[T, _, Row], R] = InsertQuery[ 145 | T, 146 | R, 147 | Ungroupped, 148 | Unordered, 149 | Unlimited, 150 | Unchainned, 151 | AssignUnchainned, 152 | HNil 153 | ] 154 | } 155 | -------------------------------------------------------------------------------- /morpheus-mysql/src/main/scala/com/outworkers/morpheus/mysql/query/UpdateQuery.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.mysql.query 17 | 18 | import com.outworkers.morpheus.mysql.{Row, Syntax} 19 | import com.outworkers.morpheus.engine.query._ 20 | import com.outworkers.morpheus.builder.SQLBuiltQuery 21 | import com.outworkers.morpheus.dsl.BaseTable 22 | import com.outworkers.morpheus.engine 23 | import shapeless.{HList, HNil} 24 | 25 | import scala.annotation.implicitNotFound 26 | 27 | case class UpdateSyntaxBlock(query: String, tableName: String) extends engine.query.RootUpdateSyntaxBlock(query, tableName) { 28 | override val syntax = Syntax 29 | 30 | def lowPriority: SQLBuiltQuery = { 31 | qb.pad.append(syntax.Priorities.lowPriority) 32 | .pad.append(tableName) 33 | } 34 | 35 | def ignore: SQLBuiltQuery = { 36 | qb.pad.append(syntax.ignore) 37 | .pad.append(tableName) 38 | } 39 | } 40 | 41 | 42 | class RootUpdateQuery[T <: BaseTable[T, _, Row], R](table: T, st: UpdateSyntaxBlock, rowFunc: Row => R) 43 | extends engine.query.RootUpdateQuery[T, R, Row](table, st, rowFunc) { 44 | 45 | def lowPriority: UpdateQuery[T, R, Ungroupped, Unordered, Unlimited, Unchainned, AssignUnchainned, HNil] = { 46 | new UpdateQuery(table, st.lowPriority, rowFunc) 47 | } 48 | 49 | def ignore: UpdateQuery[T, R, Ungroupped, Unordered, Unlimited, Unchainned, AssignUnchainned, HNil] = { 50 | new UpdateQuery(table, st.ignore, rowFunc) 51 | } 52 | } 53 | 54 | 55 | class UpdateQuery[T <: BaseTable[T, _, Row], 56 | R, 57 | Group <: GroupBind, 58 | Order <: OrderBind, 59 | Limit <: LimitBind, 60 | Chain <: ChainBind, 61 | AssignChain <: AssignBind, 62 | Status <: HList 63 | ]( 64 | table: T, 65 | query: SQLBuiltQuery, 66 | rowFunc: Row => R 67 | ) extends engine.query.UpdateQuery[T, R, Row, Group, Order, Limit, Chain, AssignChain, Status](table: T, query, rowFunc) { 68 | 69 | override def where(condition: T => QueryCondition)( 70 | implicit ev: Chain =:= Unchainned 71 | ): UpdateQuery[T, R, Group, Order, Limit, Chainned, AssignChain, Status] = { 72 | new UpdateQuery(table, table.queryBuilder.where(query, condition(table).clause), rowFunc) 73 | } 74 | 75 | override def where(condition: QueryCondition)(implicit ev: Chain =:= Unchainned): UpdateQuery[T, R, Group, Order, Limit, Chainned, 76 | AssignChain, Status] = { 77 | new UpdateQuery(table, table.queryBuilder.where(query, condition.clause), rowFunc) 78 | } 79 | 80 | override def and(condition: T => QueryCondition)(implicit ev: Chain =:= Chainned): UpdateQuery[T, R, Group, Order, Limit, Chain, 81 | AssignChainned, Status] = { 82 | new UpdateQuery(table, table.queryBuilder.and(query, condition(table).clause), rowFunc) 83 | } 84 | 85 | override def and(condition: QueryCondition)(implicit ev: Chain =:= Chainned): UpdateQuery[T, R, Group, Order, Limit, Chain, AssignChainned, 86 | Status] = { 87 | new UpdateQuery(table, table.queryBuilder.and(query, condition.clause), rowFunc) 88 | } 89 | 90 | override def set(condition: T => QueryAssignment)(implicit ev: AssignChain =:= AssignUnchainned, ev1: Status =:= HNil): UpdateQuery[T, R, 91 | Group, Order, Limit, Chain, AssignChainned, Status] = { 92 | new UpdateQuery( 93 | table, 94 | table.queryBuilder.set(query, condition(table).clause), 95 | rowFunc 96 | ) 97 | } 98 | 99 | @implicitNotFound("""You need to use the "set" method before using the "and"""") 100 | override def andSet(condition: T => QueryAssignment, signChange: Int = 0)(implicit ev: AssignChain =:= AssignChainned): UpdateQuery[T, R, 101 | Group, Order, Limit, Chain, AssignChainned, Status] = { 102 | new UpdateQuery( 103 | table, 104 | table.queryBuilder.andSet(query, condition(table).clause), 105 | rowFunc 106 | ) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /morpheus-mysql/src/test/scala/com/outworkers/morpheus/mysql/DatatypesTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.mysql 17 | 18 | import java.sql.{Date => SqlDate} 19 | import java.util.Date 20 | 21 | import com.outworkers.morpheus.builder.DefaultQueryBuilder 22 | import com.outworkers.morpheus.mysql.dsl._ 23 | import com.outworkers.morpheus.{CustomSamplers, DataType, TimePrimitive} 24 | import com.twitter.finagle.exp.mysql._ 25 | import org.joda.time.DateTime 26 | import org.scalacheck.Arbitrary 27 | import org.scalatest.prop.GeneratorDrivenPropertyChecks 28 | import org.scalatest.{Assertion, FlatSpec, Matchers, TryValues} 29 | 30 | class DatatypesTest extends FlatSpec with Matchers with GeneratorDrivenPropertyChecks with CustomSamplers with TryValues { 31 | 32 | val helper = new TimePrimitive {} 33 | 34 | def defaultFn[T]: T => String = _.toString 35 | 36 | def escaped[T](obj: T): String = DefaultQueryBuilder.escapeValue(obj.toString) 37 | 38 | def dataTypeTest[T : DataType : Arbitrary]( 39 | applier: T => Value, 40 | outcome: T => String = defaultFn[T] 41 | ): Assertion = { 42 | val dt = DataType[T] 43 | forAll { (obj: T, column: String) => 44 | val value = applier(obj) 45 | val row = Row(new EmptyRow(_ => Some(value))) 46 | 47 | dt.serialize(obj) shouldEqual outcome(obj) 48 | dt.deserialize(row, column).success.value shouldEqual obj 49 | } 50 | } 51 | 52 | it should "parse a String from a row" in { 53 | dataTypeTest[String](StringValue.apply, escaped) 54 | } 55 | 56 | it should "parse an Int from a row" in { 57 | dataTypeTest[Int](IntValue.apply) 58 | } 59 | 60 | it should "parse a Long from a row" in { 61 | dataTypeTest[Long](LongValue.apply) 62 | } 63 | 64 | it should "parse a Double from a row" in { 65 | dataTypeTest[Double](DoubleValue.apply, escaped) 66 | } 67 | 68 | it should "parse a Float from a row" in { 69 | dataTypeTest[Float](FloatValue.apply, escaped) 70 | } 71 | 72 | it should "parse a Short from a row" in { 73 | dataTypeTest[Short](ShortValue.apply) 74 | } 75 | 76 | it should "parse a Date from a row" in { 77 | val dt = DataType[Date] 78 | 79 | forAll { (date: Date, column: String) => 80 | val row = Row(new EmptyRow(_ => Some(DateValue(date.asSql)))) 81 | 82 | dt.serialize(date) shouldEqual escaped(helper.javaDateFormat.format(date)) 83 | dt.deserialize(row, column).success.value.toString shouldEqual date.toString 84 | } 85 | } 86 | 87 | ignore should "parse a DateTime from a row" in { 88 | val dt = DataType[DateTime] 89 | 90 | forAll { (date: DateTime, column: String) => 91 | val row = Row(new EmptyRow(_ => Some(DateValue(date.asSql)))) 92 | 93 | dt.serialize(date) shouldEqual escaped(date.toString(helper.jodaDateTimeFormat)) 94 | dt.deserialize(row, column).success.value shouldEqual date 95 | } 96 | } 97 | 98 | ignore should "parse an SqlDate from a row" in { 99 | forAll { (date: SqlDate, column: String) => 100 | val value = DateValue(date) 101 | val row = Row(new EmptyRow(_ => Some(value))) 102 | 103 | 104 | 105 | DataType[SqlDate].serialize(date) shouldEqual date.getTime.toString 106 | DataType[SqlDate].deserialize(row, column).success.value shouldEqual date 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /morpheus-mysql/src/test/scala/com/outworkers/morpheus/mysql/EmptyRow.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.mysql 17 | 18 | import com.twitter.finagle.exp.mysql.{ Field, Value, Row => FinagleRow } 19 | 20 | class EmptyRow(fn: String => Option[Value]) extends FinagleRow { 21 | override val fields: IndexedSeq[Field] = IndexedSeq.empty[Field] 22 | override val values: IndexedSeq[Value] = IndexedSeq.empty[Value] 23 | 24 | override def apply(columnName: String): Option[Value] = fn(columnName) 25 | 26 | override def indexOf(columnName: String): Option[Int] = None 27 | } 28 | -------------------------------------------------------------------------------- /morpheus-mysql/src/test/scala/com/outworkers/morpheus/mysql/db/Connector.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus 17 | 18 | package mysql 19 | package db 20 | 21 | import java.util.concurrent.TimeUnit 22 | import java.util.concurrent.atomic.AtomicBoolean 23 | 24 | import sys.process._ 25 | import com.twitter.finagle.exp.Mysql 26 | import org.scalatest.concurrent.{PatienceConfiguration, ScalaFutures, Waiters} 27 | import org.scalatest.time.{Millis, Seconds, Span} 28 | import org.scalatest.{BeforeAndAfterAll, Matchers, OptionValues, Suite} 29 | import org.slf4j.LoggerFactory 30 | 31 | object Connector { 32 | 33 | private[this] val init = new AtomicBoolean(false) 34 | 35 | private[this] val logger = LoggerFactory.getLogger(this.getClass) 36 | 37 | def isRunningUnderTravis: Boolean = sys.env.contains("TRAVIS") 38 | 39 | private[this] val databaseName = "morpheus_test" 40 | private[this] val user = if (isRunningUnderTravis) "travis" else "root" 41 | private[this] val pwd = "morpheus23!" 42 | 43 | /** 44 | * This client is meant to connect to the Travis CI default MySQL service. 45 | */ 46 | lazy val client = Mysql.client 47 | .withCredentials(user, pwd) 48 | .withDatabase(databaseName) 49 | .newRichClient("127.0.0.1:3306") 50 | 51 | def initialise(): Unit = { 52 | if (!isRunningUnderTravis && init.compareAndSet(false, true)) { 53 | logger.info("Initialising process database") 54 | val procs = List( 55 | s"""mysql -e "CREATE DATABASE IF NOT EXISTS $databaseName;" """, 56 | s"""mysql -e "CREATE USER IF NOT EXISTS '$user'@'localhost' IDENTIFIED BY '$pwd';" """, 57 | s"""mysql -e GRANT ALL PRIVILEGES ON * . * TO '$user'@'localhost'""", 58 | s"""mysql -e "SET PASSWORD FOR '$user'@'localhost' = PASSWORD('$pwd')"""" 59 | ) 60 | 61 | procs.foreach { cmd => 62 | val proc = cmd.! 63 | 64 | if (proc != 0) { 65 | logger.error(s"Failed to initialise the database with user $user") 66 | } else { 67 | logger.info(s"Successfully initialised the local database $databaseName with user $user") 68 | } 69 | } 70 | 71 | } else { 72 | logger.info("Local database is already initialised.") 73 | } 74 | } 75 | } 76 | 77 | trait BaseSuite extends Waiters 78 | with ScalaFutures 79 | with OptionValues 80 | with Matchers 81 | with BeforeAndAfterAll { 82 | 83 | this: Suite => 84 | 85 | implicit lazy val client = new mysql.Client(Connector.client) 86 | 87 | protected[this] val defaultScalaTimeoutSeconds = 10 88 | 89 | private[this] val defaultScalaInterval = 50L 90 | 91 | implicit val defaultScalaTimeout = scala.concurrent.duration.Duration(defaultScalaTimeoutSeconds, TimeUnit.SECONDS) 92 | 93 | private[this] val defaultTimeoutSpan = Span(defaultScalaTimeoutSeconds, Seconds) 94 | 95 | implicit val defaultTimeout: PatienceConfiguration.Timeout = timeout(defaultTimeoutSpan) 96 | 97 | implicit val context = scala.concurrent.ExecutionContext.Implicits.global 98 | 99 | override implicit val patienceConfig = PatienceConfig( 100 | timeout = defaultTimeoutSpan, 101 | interval = Span(defaultScalaInterval, Millis) 102 | ) 103 | 104 | override def beforeAll(): Unit = { 105 | super.beforeAll() 106 | // Connector.initialise() 107 | } 108 | } 109 | 110 | // CREATE USER 'morpheus'@'localhost' IDENTIFIED BY 'morpheus23!'; 111 | // GRANT ALL PRIVILEGES ON * . * TO 'morpheus'@'localhost'; 112 | -------------------------------------------------------------------------------- /morpheus-mysql/src/test/scala/com/outworkers/morpheus/mysql/db/CreateQueryTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.mysql.db 17 | 18 | import com.outworkers.morpheus.mysql.tables.BasicTable 19 | import org.scalatest.FlatSpec 20 | import com.outworkers.morpheus.mysql.dsl._ 21 | 22 | class CreateQueryTest extends FlatSpec with BaseSuite { 23 | 24 | it should "create a new table in the MySQL database" in { 25 | whenReady(BasicTable.create.temporary.engine(InnoDB).future) { 26 | res => 27 | } 28 | } 29 | 30 | it should "create a new table in the database if the table doesn't exist" in { 31 | whenReady(BasicTable.create.ifNotExists.engine(InnoDB).future) { _ => } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /morpheus-mysql/src/test/scala/com/outworkers/morpheus/mysql/db/InsertQueryDBTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.mysql.db 17 | 18 | import com.outworkers.morpheus.CustomSamplers 19 | import com.outworkers.morpheus.mysql.dsl._ 20 | import com.outworkers.morpheus.mysql.tables.{BasicRecord, BasicTable, PrimitiveRecord, PrimitivesTable} 21 | import com.outworkers.util.samplers._ 22 | import org.scalatest.FlatSpec 23 | import org.scalatest.prop.GeneratorDrivenPropertyChecks 24 | 25 | import scala.concurrent.Await 26 | import scala.concurrent.duration._ 27 | import scala.math.BigDecimal.RoundingMode 28 | import org.joda.time.{ DateTime, DateTimeZone } 29 | 30 | class InsertQueryDBTest extends FlatSpec with BaseSuite with GeneratorDrivenPropertyChecks with CustomSamplers { 31 | 32 | override def beforeAll(): Unit = { 33 | super.beforeAll() 34 | Await.result(BasicTable.create.ifNotExists.engine(InnoDB).future(), 3.seconds) 35 | Await.result(PrimitivesTable.create.ifNotExists.engine(InnoDB).future(), 3.seconds) 36 | } 37 | 38 | implicit val datetimeSampler = new Sample[DateTime] { 39 | override def sample: DateTime = DateTime.now(DateTimeZone.UTC) 40 | } 41 | 42 | it should "store a record in the database and retrieve it by id" in { 43 | val sample = gen[BasicRecord] 44 | 45 | val chain = for { 46 | store <- BasicTable.insert.value(_.name, sample.name).value(_.count, sample.count).future() 47 | one <- BasicTable.select.where(_.name eqs sample.name).one() 48 | } yield one 49 | 50 | whenReady(chain) { res => 51 | res.value shouldEqual sample 52 | } 53 | } 54 | 55 | it should "insert and select a record with all the primitive types in MySQL" in { 56 | 57 | val fl = gen[BigDecimal].setScale(2, RoundingMode.HALF_UP).toFloat 58 | val sample = gen[PrimitiveRecord].copy(float = fl) 59 | 60 | val chain = for { 61 | store <- PrimitivesTable.store(sample).future() 62 | one <- PrimitivesTable.select.where(_.id eqs sample.id).one() 63 | } yield one 64 | 65 | whenReady(chain) { res => 66 | res.value.id shouldEqual sample.id 67 | res.value.double shouldEqual sample.double 68 | res.value.long shouldEqual sample.long 69 | res.value.str shouldEqual sample.str 70 | res.value.float shouldEqual fl 71 | // res.value.datetime shouldEqual sample.datetime 72 | // res.value.date shouldEqual sample.date 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /morpheus-mysql/src/test/scala/com/outworkers/morpheus/mysql/db/SelectQueryTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.mysql.db 17 | 18 | import com.outworkers.morpheus.mysql.tables.{BasicRecord, BasicTable} 19 | import com.outworkers.util.samplers._ 20 | import com.outworkers.morpheus.mysql.dsl._ 21 | import org.scalatest.FlatSpec 22 | 23 | import scala.concurrent.Await 24 | import scala.concurrent.duration._ 25 | 26 | class SelectQueryTest extends FlatSpec with BaseSuite { 27 | 28 | override def beforeAll(): Unit = { 29 | super.beforeAll() 30 | Await.result(BasicTable.create.ifNotExists.engine(InnoDB).future(), 10.seconds) 31 | } 32 | 33 | it should "store a record in the database and retrieve it by id" in { 34 | val sample = gen[BasicRecord] 35 | 36 | val chain = for { 37 | store <- BasicTable.insert.value(_.name, sample.name).value(_.count, sample.count).future() 38 | one <- BasicTable.select.where(_.name eqs sample.name).one() 39 | } yield one 40 | 41 | whenReady(chain) { res => 42 | res.value shouldEqual sample 43 | } 44 | } 45 | 46 | it should "store a record in the database and partially retrieve 1 column" in { 47 | val sample = gen[BasicRecord] 48 | 49 | val chain = for { 50 | store <- BasicTable.insert.value(_.name, sample.name).value(_.count, sample.count).future() 51 | one <- BasicTable.select(_.name).where(_.name eqs sample.name).one() 52 | } yield one 53 | 54 | whenReady(chain) { res => 55 | res.value shouldEqual sample.name 56 | } 57 | } 58 | 59 | ignore should "store a record in the database and partially retrieve 2 columns" in { 60 | val sample = gen[BasicRecord] 61 | 62 | val chain = for { 63 | store <- BasicTable.insert.value(_.name, sample.name).value(_.count, sample.count).future() 64 | one <- BasicTable.select(_.name, _.count).where(_.name eqs sample.name).one() 65 | } yield one 66 | 67 | whenReady(chain) { res => 68 | res.value shouldEqual sample.name -> sample.count 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /morpheus-mysql/src/test/scala/com/outworkers/morpheus/mysql/db/UpdateQueryTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.mysql.db 17 | 18 | import org.scalatest.FlatSpec 19 | 20 | class UpdateQueryTest extends FlatSpec with BaseSuite { 21 | 22 | } 23 | -------------------------------------------------------------------------------- /morpheus-mysql/src/test/scala/com/outworkers/morpheus/mysql/db/specialized/EnumerationColumnTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.mysql.db.specialized 17 | 18 | import com.outworkers.morpheus.DataType 19 | import com.outworkers.morpheus.mysql.dsl._ 20 | import com.outworkers.morpheus.mysql.db.BaseSuite 21 | import com.outworkers.morpheus.mysql.tables.{EnumerationRecord, EnumerationTable, TestEnumeration} 22 | import com.outworkers.util.samplers.{Generators, Sample} 23 | import com.outworkers.util.samplers._ 24 | import org.scalatest.FlatSpec 25 | 26 | import scala.concurrent.Await 27 | import scala.concurrent.duration._ 28 | 29 | class EnumerationColumnTest extends FlatSpec with BaseSuite { 30 | 31 | override def beforeAll(): Unit = { 32 | super.beforeAll() 33 | Await.result(EnumerationTable.create.ifNotExists.engine(InnoDB).future(), 5.seconds) 34 | } 35 | 36 | implicit object EnumerationRecordSampler extends Sample[EnumerationRecord] { 37 | override def sample: EnumerationRecord = EnumerationRecord(gen[Int], Generators.oneOf(TestEnumeration)) 38 | } 39 | 40 | implicit val enumPrimitive: DataType[TestEnumeration#Value] = SQLPrimitive(TestEnumeration) 41 | 42 | it should "store a record with an enumeration defined inside it" in { 43 | val record = gen[EnumerationRecord] 44 | 45 | val chain = for { 46 | store <- EnumerationTable.store(record).future() 47 | get <- EnumerationTable.select.where(_.id eqs record.id).one 48 | } yield get 49 | 50 | whenReady(chain) { res => 51 | res.value shouldEqual record 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /morpheus-mysql/src/test/scala/com/outworkers/morpheus/mysql/query/InsertQueryTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.mysql.query 17 | 18 | import com.outworkers.morpheus.mysql.tables.BasicTable 19 | import com.outworkers.util.samplers._ 20 | import com.outworkers.morpheus.mysql.dsl._ 21 | import org.scalatest.{FlatSpec, Matchers} 22 | 23 | class InsertQueryTest extends FlatSpec with Matchers { 24 | 25 | it should "serialise an INSERT IGNORE INTO query to the correct query" in { 26 | BasicTable.insert.ignore.queryString shouldEqual "INSERT IGNORE INTO BasicTable;" 27 | } 28 | 29 | it should "serialise an INSERT HIGH_PRIORITY INTO query to the correct query" in { 30 | BasicTable.insert.highPriority.queryString shouldEqual "INSERT HIGH_PRIORITY INTO BasicTable;" 31 | } 32 | 33 | it should "serialise an INSERT LOW_PRIORITY INTO query to the correct query" in { 34 | BasicTable.insert.lowPriority.queryString shouldEqual "INSERT LOW_PRIORITY INTO BasicTable;" 35 | } 36 | 37 | it should "serialise an INSERT DELAYED INTO query to the correct query" in { 38 | BasicTable.insert.delayed.queryString shouldEqual "INSERT DELAYED INTO BasicTable;" 39 | } 40 | 41 | it should "serialise an INSERT value query with a single value clause" in { 42 | BasicTable.insert.value(_.name, "test").queryString shouldEqual "INSERT INTO `BasicTable` (`name`) VALUES('test');" 43 | } 44 | 45 | it should "serialise an INSERT value query with a multiple value clauses" in { 46 | val num = gen[Int] 47 | BasicTable.insert.value(_.name, "test").value(_.count, num) 48 | .queryString shouldEqual s"INSERT INTO `BasicTable` (`name`, `count`) VALUES('test', $num);" 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /morpheus-mysql/src/test/scala/com/outworkers/morpheus/mysql/query/parts/PartsTest.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.outworkers.morpheus.mysql.query.parts 17 | 18 | import com.outworkers.morpheus.builder.SQLBuiltQuery 19 | import com.outworkers.morpheus.engine.query.parts.MergeList 20 | import org.scalatest.prop.GeneratorDrivenPropertyChecks 21 | import org.scalatest.{FlatSpec, Matchers} 22 | 23 | class PartsTest extends FlatSpec with Matchers with GeneratorDrivenPropertyChecks { 24 | 25 | it should "evaluate nonEmpty to true on a merge list of the inner list is empty" in { 26 | forAll { l: List[String] => 27 | val part = MergeList(l.map(SQLBuiltQuery.apply)) 28 | part.nonEmpty shouldEqual l.nonEmpty 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /morpheus-mysql/src/test/scala/com/outworkers/morpheus/mysql/tables/PrimitivesTable.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2017 Outworkers, Limited. 3 | * 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * - Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * 12 | * - Redistributions in binary form must reproduce the above copyright 13 | * notice, this list of conditions and the following disclaimer in the 14 | * documentation and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | package com.outworkers.morpheus.mysql.tables 29 | 30 | import com.outworkers.morpheus.keys.Autoincrement 31 | import org.joda.time.DateTime 32 | import com.outworkers.morpheus.mysql.dsl._ 33 | import com.outworkers.morpheus.mysql.query.InsertQuery 34 | 35 | case class PrimitiveRecord( 36 | id: Int, 37 | num: Int, 38 | double: Double, 39 | real: Double, 40 | float: Float, 41 | long: Long, 42 | date: java.util.Date, 43 | dateTime: DateTime, 44 | str: String 45 | ) 46 | 47 | class PrimitivesTable extends Table[PrimitivesTable, PrimitiveRecord] { 48 | object id extends IntColumn(this) with PrimaryKey with Autoincrement with NotNull 49 | object num extends IntColumn(this) 50 | object doubleColumn extends DoubleColumn(this) 51 | object floatColumn extends FloatColumn(this, 10) 52 | object realColumn extends RealColumn(this) 53 | object longColumn extends LongColumn(this) 54 | object dateColumn extends DateColumn(this) 55 | object datetimeColumn extends DateTimeColumn(this) 56 | object strColumn extends VarcharColumn(this, 256) 57 | 58 | override def tableName: String = "primitives_table" 59 | 60 | /** 61 | * The most notable and honorable of functions in this file, this is what allows our DSL to provide type-safety. 62 | * It works by requiring a user to define a type-safe mapping between a buffered Result and the above refined Record. 63 | * 64 | * Objects delimiting pre-defined columns also have a pre-defined "apply" method, allowing the user to simply autofill the type-safe mapping by using 65 | * pre-existing definitions. 66 | * 67 | * @param row The row incoming as a result from a MySQL query. 68 | * @return A Record instance. 69 | */ 70 | override def fromRow(row: Row): PrimitiveRecord = { 71 | PrimitiveRecord( 72 | id(row), 73 | num(row), 74 | doubleColumn(row), 75 | realColumn(row), 76 | floatColumn(row), 77 | longColumn(row), 78 | dateColumn(row), 79 | datetimeColumn(row), 80 | strColumn(row) 81 | ) 82 | } 83 | 84 | def store(rec: PrimitiveRecord): InsertQuery.Default[PrimitivesTable, PrimitiveRecord] = { 85 | insert 86 | .value(_.id, rec.id) 87 | .value(_.num, rec.num) 88 | .value(_.doubleColumn, rec.double) 89 | .value(_.realColumn, rec.real) 90 | .value(_.floatColumn, rec.float) 91 | .value(_.longColumn, rec.long) 92 | .value(_.dateColumn, rec.date) 93 | .value(_.datetimeColumn, rec.dateTime) 94 | .value(_.strColumn, rec.str) 95 | } 96 | } 97 | 98 | object PrimitivesTable extends PrimitivesTable -------------------------------------------------------------------------------- /morpheus.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outworkers/morpheus/92196e3b7b6469fe456993da284d72c58e94b6e5/morpheus.graffle -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.18 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 - 2019 Outworkers Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | resolvers ++= Seq( 17 | "jgit-repo" at "http://download.eclipse.org/jgit/maven", 18 | "Twitter Repo" at "http://maven.twttr.com/", 19 | Resolver.sonatypeRepo("releases"), 20 | Resolver.bintrayRepo("outworkers", "oss-releases"), 21 | Classpaths.sbtPluginReleases 22 | ) 23 | 24 | addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.5.6") 25 | 26 | addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.8.2") 27 | 28 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.0") 29 | 30 | addSbtPlugin("org.scoverage" %% "sbt-coveralls" % "1.1.0") 31 | 32 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.0.0") 33 | 34 | addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "0.7.0") 35 | 36 | addSbtPlugin("com.typesafe.sbt" % "sbt-git" % "0.8.5") 37 | 38 | addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.4") 39 | 40 | addSbtPlugin("com.eed3si9n" % "sbt-doge" % "0.1.5") 41 | 42 | addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.5") 43 | 44 | libraryDependencies += "org.slf4j" % "slf4j-nop" % "1.7.22" 45 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.scoverage.reportPath=target/scala-2.10/scoverage-report/scoverage.xml 2 | sonar.projectKey=MPH 3 | sonar.projectName=Morpheus 4 | sonar.projectVersion=0.1.0 5 | sonar.sources=morpheus-dsl/src/main/scala/ 6 | 7 | --------------------------------------------------------------------------------