├── example-simulations ├── project │ ├── build.properties │ └── plugins.sbt ├── images │ ├── gatling-summary.png │ └── gatling-response.png ├── build.sbt ├── src │ └── test │ │ ├── resources │ │ ├── logback.xml │ │ └── recorder.conf │ │ └── scala │ │ └── kwery │ │ └── QuerySimulation.scala └── README.md ├── gradle.properties ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties └── gradlew.bat ├── settings.gradle ├── .gitignore ├── mapper └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── github │ │ └── andrewoma │ │ └── kwery │ │ └── mapper │ │ ├── Value.kt │ │ ├── TableConfiguration.kt │ │ ├── OptimisticLockException.kt │ │ ├── TimeDefaults.kt │ │ ├── Versioned.kt │ │ ├── util │ │ └── CaseFormatters.kt │ │ ├── Defaults.kt │ │ ├── Column.kt │ │ ├── Columns.kt │ │ ├── Dao.kt │ │ ├── KeyRow.kt │ │ └── listener │ │ └── DaoListener.kt │ └── test │ ├── kotlin │ └── com │ │ └── github │ │ └── andrewoma │ │ └── kwery │ │ └── mappertest │ │ ├── issues │ │ └── Issue11_12.kt │ │ ├── example │ │ ├── test │ │ │ ├── util │ │ │ │ └── CaseFormattersTest.kt │ │ │ ├── LanguageDaoTest.kt │ │ │ ├── FilmActorDaoTest.kt │ │ │ └── ActorDaoTest.kt │ │ ├── LanguageDao.kt │ │ ├── Config.kt │ │ ├── FilmActorDao.kt │ │ ├── FimGraphFetcher.kt │ │ ├── Model.kt │ │ └── ActorDao.kt │ │ └── AbstractSessionTest.kt │ └── resources │ └── logback.xml ├── .travis.yml ├── example └── src │ ├── main │ ├── resources │ │ ├── languages.json │ │ ├── dev.yml │ │ └── schema.sql │ └── kotlin │ │ └── com │ │ └── github │ │ └── andrewoma │ │ └── kwery │ │ └── example │ │ └── film │ │ ├── Main.kt │ │ ├── dao │ │ ├── GuavaCache.kt │ │ ├── LanguageDao.kt │ │ ├── Config.kt │ │ ├── ActorDao.kt │ │ ├── FilmActorDao.kt │ │ └── FilmDao.kt │ │ ├── FilmConfiguration.kt │ │ ├── resources │ │ ├── Resource.kt │ │ ├── ActorResource.kt │ │ ├── FilmResource.kt │ │ └── LanguageResource.kt │ │ ├── jersey │ │ ├── SqlExceptionMapper.kt │ │ └── LoggingListener.kt │ │ ├── model │ │ └── FilmModel.kt │ │ └── jackson │ │ └── JacksonExtensions.kt │ └── test │ └── kotlin │ └── com │ └── github │ └── andrewoma │ └── kwery │ └── example │ └── film │ └── FilmApplicationAcceptanceTest.kt ├── LICENSE.txt ├── transactional-jersey ├── README.md └── src │ └── test │ └── resources │ └── logback.xml ├── core └── src │ ├── main │ └── kotlin │ │ └── com │ │ └── github │ │ └── andrewoma │ │ └── kwery │ │ └── core │ │ ├── builder │ │ ├── Query.kt │ │ └── Filter.kt │ │ ├── Cache.kt │ │ ├── interceptor │ │ ├── StatementInterceptor.kt │ │ └── StatementInterceptorChain.kt │ │ ├── SessionFactory.kt │ │ ├── dialect │ │ ├── MysqlDialect.kt │ │ ├── SqliteDialect.kt │ │ ├── HsqlDialect.kt │ │ └── PostgresDialect.kt │ │ └── BoundQuery.kt │ └── test │ ├── kotlin │ └── com │ │ └── github │ │ └── andrewoma │ │ └── kwery │ │ └── core │ │ ├── ThreadLocalSessionDelegationTest.kt │ │ ├── Datasources.kt │ │ ├── SessionFactoryTest.kt │ │ ├── ReadmeExamplesTest.kt │ │ ├── ManualTransactionTest.kt │ │ ├── ManagedThreadLocalSessionDelegationTest.kt │ │ ├── SqliteDialectTest.kt │ │ ├── MysqlDialectTest.kt │ │ ├── PostgresDialectTest.kt │ │ ├── TransactionBlockTest.kt │ │ ├── BatchUpdateTest.kt │ │ ├── HsqlDialectTest.kt │ │ ├── ReentrantTransactionTest.kt │ │ ├── AbstractSessionTest.kt │ │ └── AbstractFilmSessionTest.kt │ └── resources │ └── logback.xml ├── fetcher └── src │ ├── test │ ├── resources │ │ └── logback.xml │ └── kotlin │ │ └── com │ │ └── github │ │ └── andrewoma │ │ └── kwery │ │ └── fetcher │ │ ├── NodeTest.kt │ │ └── readme │ │ └── Readme.kt │ └── main │ └── kotlin │ └── com │ └── github │ └── andrewoma │ └── kwery │ └── fetcher │ └── Type.kt ├── transactional ├── src │ ├── test │ │ └── resources │ │ │ └── logback.xml │ └── main │ │ └── kotlin │ │ └── com │ │ └── github │ │ └── andrewoma │ │ └── kwery │ │ └── transactional │ │ ├── Transactional.kt │ │ ├── transactionalFactory.kt │ │ └── TransactionalInterceptor.kt └── README.md ├── publish.gradle └── gradlew.bat /example-simulations/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | #Sat, 05 Aug 2017 19:43:41 +1000 2 | version=0.18-SNAPSHOT 3 | -------------------------------------------------------------------------------- /example-simulations/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("io.gatling" % "gatling-sbt" % "2.1.0") 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewoma/kwery/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewoma/kwery/HEAD/gradle/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example-simulations/images/gatling-summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewoma/kwery/HEAD/example-simulations/images/gatling-summary.png -------------------------------------------------------------------------------- /example-simulations/images/gatling-response.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andrewoma/kwery/HEAD/example-simulations/images/gatling-response.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include 'core' 2 | include 'fetcher' 3 | include 'mapper' 4 | include 'example' 5 | include 'transactional' 6 | include 'transactional-jersey' 7 | 8 | rootProject.name = 'kwery' 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .gradle 3 | *.iml 4 | *.ipr 5 | *.iws 6 | *.classpath 7 | *.project 8 | *.settings 9 | .DS_Store 10 | .textmate 11 | .idea 12 | *.sublime-* 13 | .nb-gradle 14 | build 15 | target 16 | classes -------------------------------------------------------------------------------- /mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Value.kt: -------------------------------------------------------------------------------- 1 | package com.github.andrewoma.kwery.mapper 2 | 3 | /** 4 | * Value allows extraction of column values by column. 5 | */ 6 | interface Value { 7 | infix fun of(column: Column): T 8 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Dec 08 16:17:22 AEDT 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-bin.zip 7 | -------------------------------------------------------------------------------- /gradle/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Oct 22 20:37:18 EST 2014 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-bin.zip 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | 5 | before_script: 6 | - psql -c 'create database kwery;' -U postgres 7 | - mysql -uroot -e "create database if not exists kwery" 8 | - mysql -uroot -e "create user 'kwery'@'localhost' identified by 'kwery'" 9 | - mysql -uroot -e "grant all privileges on *.* to 'kwery'@'localhost'" 10 | 11 | script: 12 | - ./gradlew --info check 13 | 14 | services: 15 | # - postgresql 16 | - mysql 17 | 18 | addons: 19 | postgresql: "9.3" -------------------------------------------------------------------------------- /example-simulations/build.sbt: -------------------------------------------------------------------------------- 1 | enablePlugins(GatlingPlugin) 2 | 3 | scalaVersion := "2.11.5" 4 | 5 | scalacOptions := Seq( 6 | "-encoding", "UTF-8", "-target:jvm-1.7", "-deprecation", 7 | "-feature", "-unchecked", "-language:implicitConversions", "-language:postfixOps") 8 | 9 | libraryDependencies += "io.gatling.highcharts" % "gatling-charts-highcharts" % "2.1.4" % "test" 10 | libraryDependencies += "io.gatling" % "gatling-test-framework" % "2.1.4" % "test" 11 | -------------------------------------------------------------------------------- /example/src/main/resources/languages.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "English", 5 | "version": 1 6 | }, 7 | { 8 | "id": 2, 9 | "name": "Italian", 10 | "version": 1 11 | }, 12 | { 13 | "id": 3, 14 | "name": "Japanese", 15 | "version": 1 16 | }, 17 | { 18 | "id": 4, 19 | "name": "Mandarin", 20 | "version": 1 21 | }, 22 | { 23 | "id": 5, 24 | "name": "French", 25 | "version": 1 26 | }, 27 | { 28 | "id": 6, 29 | "name": "German", 30 | "version": 1 31 | } 32 | ] -------------------------------------------------------------------------------- /example/src/main/resources/dev.yml: -------------------------------------------------------------------------------- 1 | database: 2 | driverClass: org.hsqldb.jdbc.JDBCDriver 3 | url: jdbc:hsqldb:mem:kweryexample 4 | user: 5 | maxWaitForConnection: 1s 6 | validationQuery: "select 1 from information_schema.system_users" 7 | minSize: 1 8 | maxSize: 32 9 | logValidationErrors: true 10 | 11 | server: 12 | applicationContextPath: /api/* 13 | applicationConnectors: 14 | - type: http 15 | port: 9090 16 | adminConnectors: 17 | - type: http 18 | port: 9091 19 | 20 | logging: 21 | # The default level of all loggers. Can be OFF, ERROR, WARN, INFO, DEBUG, TRACE, or ALL. 22 | level: INFO 23 | 24 | # loggers: 25 | # com.example.app: DEBUG 26 | -------------------------------------------------------------------------------- /example-simulations/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %d{HH:mm:ss.SSS} [%-5level] %logger{15} - %msg%n%rEx 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Andrew O'Malley 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/TableConfiguration.kt: -------------------------------------------------------------------------------- 1 | package com.github.andrewoma.kwery.mapper 2 | 3 | import com.github.andrewoma.kwery.mapper.util.camelToLowerUnderscore 4 | import kotlin.reflect.KType 5 | 6 | /** 7 | * TableConfiguration defines configuration common to a set of tables. 8 | */ 9 | class TableConfiguration( 10 | /** 11 | * Defines default values for types when the column is not null, but is not selected. 12 | * Defaults to `standardDefaults` 13 | */ 14 | val defaults: Map = standardDefaults + timeDefaults, 15 | 16 | /** 17 | * Defines converters from JDBC types to arbitrary Kotlin types. 18 | * Defaults to `standardConverters` + `timeConverters` 19 | */ 20 | val converters: Map, Converter<*>> = standardConverters + timeConverters, 21 | 22 | /** 23 | * Defines the naming convention for converting `Column` names to SQL column names. 24 | * Defaults to `camelToLowerUnderscore` 25 | */ 26 | val namingConvention: (String) -> String = camelToLowerUnderscore) -------------------------------------------------------------------------------- /example/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | create table actor ( 2 | id integer identity, 3 | first_name varchar(255) not null, 4 | last_name varchar(255) null, 5 | version integer not null 6 | ); 7 | 8 | create table language ( 9 | id integer identity, 10 | name varchar(255) not null, 11 | version integer not null, 12 | constraint language_name_idx unique (name) 13 | ); 14 | 15 | create table film ( 16 | id integer identity, 17 | title varchar(255) not null, 18 | description varchar(4000) not null, 19 | release_year integer, 20 | language_id integer not null, 21 | original_language_id integer, 22 | length integer, 23 | rating varchar(255), 24 | special_features varchar(255) array, 25 | version integer not null, 26 | constraint fk_film_language foreign key (language_id) references language (id), 27 | constraint fk_film_orig_language foreign key (original_language_id) references language (id) 28 | ); 29 | 30 | create table film_actor ( 31 | film_id integer not null, 32 | actor_id integer not null, 33 | primary key (film_id, actor_id), 34 | constraint fk_fa_film foreign key (film_id) references film (id), 35 | constraint fk_fa_actor foreign key (actor_id) references actor (id) 36 | ) 37 | -------------------------------------------------------------------------------- /transactional-jersey/README.md: -------------------------------------------------------------------------------- 1 | The transactional-jersey module provides transactional interceptors for kwery with Jersey 2. 2 | 3 | Note: the use of this module is discouraged as it may be removed - using a `ThreadLocalSession` with 4 | [transaction blocks](../core#transactions) is preferred as they work without interceptor 5 | configuration. 6 | 7 | Registering [TransactionListener](src/main/kotlin/com/github/andrewoma/kwery/transactional/jersey/transactional.kt) 8 | as a Jersey provider allows the `transactional` attribute to declare resource classes (or methods) as transactional. 9 | 10 | Any `ManagedThreadLocalSessions` will then automatically participate in a transaction. Transactions will 11 | be rolled back automatically if the status of the method is not successful. 12 | 13 | ```kotlin 14 | val session = ManagedThreadLocalSession(dataSource, HsqlDialect()) 15 | 16 | Path("/films") 17 | transactional class FilmResource(session: Session) : Resource { 18 | GET fun find(): List { 19 | session.select(...) 20 | } 21 | } 22 | ``` 23 | 24 | The `transaction` annotation accepts 2 parameters: 25 | - `manual`. If true, a session will be initialised but no transaction will be started. This allows the use 26 | of `Session's` transactional methods to manually control transaction boundaries. 27 | - `name`. Multiple datasources can be supported via the `name` attribute (name underlying `ManagedThreadLocalSessions` to match). -------------------------------------------------------------------------------- /mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/issues/Issue11_12.kt: -------------------------------------------------------------------------------- 1 | package com.github.andrewoma.kwery.mappertest.issues 2 | 3 | import com.github.andrewoma.kwery.mapper.* 4 | import org.junit.Test 5 | import java.util.* 6 | 7 | class Issue11_12 { 8 | 9 | @Test(expected = IllegalStateException::class) 10 | fun `should reject adding more columns after table construction`() { 11 | val usersTable = UsersTable() 12 | usersTable.allColumns // Forces instance construction 13 | 14 | val emailColumn = Column( 15 | User::email, null, 16 | optional(stringConverter), 17 | "email", false, false, true, false 18 | ) 19 | 20 | usersTable.addColumn(emailColumn) 21 | } 22 | 23 | @Test fun `should return allColumns after table construction`() { 24 | val usersTable = UsersTable() 25 | assert(usersTable.allColumns.isNotEmpty()) 26 | } 27 | } 28 | 29 | private data class User(val id: String, val name: String, val age: Int, val email: String? = null) 30 | 31 | private class UsersTable : Table("users") { 32 | 33 | val id by col(User::id, id = true) 34 | val userName by col(User::name) 35 | val age by col(User::age) 36 | 37 | override fun create(value: Value) = User(value of id, value of userName, value of age) 38 | 39 | override fun idColumns(id: UUID) = setOf(this.id to id) 40 | } 41 | -------------------------------------------------------------------------------- /core/src/main/kotlin/com/github/andrewoma/kwery/core/builder/Query.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core.builder 24 | 25 | class Query(val sqlBuffer: StringBuilder, val parameters: Map) { 26 | val sql: String by lazy { sqlBuffer.toString() } 27 | } -------------------------------------------------------------------------------- /example-simulations/README.md: -------------------------------------------------------------------------------- 1 | ### Kwery Example Performance Tests 2 | 3 | This module uses [Gatling](http://gatling.io/) to run a few performance tests against the example module. 4 | 5 | It requires `sbt` to build and run (I use [Paul Phillips' sbt-extras script](https://github.com/paulp/sbt-extras)). 6 | 7 | #### Start the example server 8 | 9 | Before running a simulation, make sure the example server is up and running in another terminal window. 10 | 11 | ```bash 12 | $ cd .. 13 | $ ./gradlew :example:run 14 | ``` 15 | 16 | #### Running simulations 17 | 18 | Start `sbt` to run simulations: 19 | 20 | ```bash 21 | $ sbt 22 | ``` 23 | 24 | ##### Compile the simulations 25 | 26 | ```bash 27 | > compile 28 | ``` 29 | 30 | ##### Run all simulations 31 | 32 | ```bash 33 | > test 34 | ``` 35 | 36 | ##### Run a single simulation 37 | 38 | ```bash 39 | > testOnly kwery.QuerySimulation 40 | ``` 41 | 42 | ##### Open the last report 43 | 44 | ```bash 45 | > lastReport 46 | ``` 47 | 48 | ##### Available simulations 49 | 50 | * kwery.QuerySimulation: Runs a few queries for 4 minutes at around 300 requests/s 51 | 52 | ##### Sample Reports 53 | 54 | Gatling produces excellent reports, allowing analysis per request and zooming. Here's a couple of static 55 | screen grabs of the `kwery.QuerySimulation` report: 56 | 57 | ![Gatling Summary](images/gatling-summary.png "Gatling Summary") 58 | ![Gatling Response](images/gatling-response.png "Gatling Response") 59 | -------------------------------------------------------------------------------- /mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/OptimisticLockException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mapper 24 | 25 | import java.sql.SQLNonTransientException 26 | 27 | class OptimisticLockException(message: String) : SQLNonTransientException(message) -------------------------------------------------------------------------------- /example/src/main/kotlin/com/github/andrewoma/kwery/example/film/Main.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.example.film 24 | 25 | import java.io.File 26 | 27 | fun main(args: Array) { 28 | // Default to dev config if unspecified and it's available 29 | val config = "example/src/main/resources/dev.yml" 30 | val defaults = arrayOf("server", config) 31 | 32 | FilmApplication().run(*if (args.isEmpty() && File(config).exists()) defaults else args) 33 | } 34 | -------------------------------------------------------------------------------- /core/src/test/kotlin/com/github/andrewoma/kwery/core/ThreadLocalSessionDelegationTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core 24 | 25 | import com.github.andrewoma.kwery.core.dialect.PostgresDialect 26 | 27 | class ThreadLocalSessionDelegationTest : AbstractSessionDelegationTest() { 28 | companion object { 29 | val session = ThreadLocalSession(postgresDataSource, PostgresDialect(), postgresLoggingInterceptor) 30 | } 31 | 32 | override fun withSession(f: (Session) -> R) = f(session) 33 | } -------------------------------------------------------------------------------- /core/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 26 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /fetcher/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 26 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /mapper/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 26 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /transactional/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 26 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /transactional-jersey/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 25 | 26 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/TimeDefaults.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mapper 24 | 25 | import java.time.* 26 | import kotlin.reflect.KType 27 | 28 | val timeDefaults: Map = listOf( 29 | reifiedValue(LocalTime.now()), 30 | reifiedValue(LocalDate.now()), 31 | reifiedValue(LocalDateTime.now()), 32 | reifiedValue(Duration.ZERO), 33 | reifiedValue(Instant.now()), 34 | reifiedValue(OffsetDateTime.now()), 35 | reifiedValue(ZonedDateTime.now()) 36 | ).toMap() 37 | 38 | -------------------------------------------------------------------------------- /example/src/main/kotlin/com/github/andrewoma/kwery/example/film/dao/GuavaCache.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.example.film.dao 24 | 25 | import com.github.andrewoma.kwery.core.Cache 26 | import com.google.common.cache.CacheBuilder 27 | import com.google.common.cache.Cache as GCache 28 | 29 | class GuavaCache(val underlying: GCache = CacheBuilder.newBuilder().build()) : Cache { 30 | 31 | override fun get(key: K) = underlying.getIfPresent(key) 32 | override fun getOrPut(key: K, ifAbsent: (K) -> V) = underlying[key, { ifAbsent(key) }] 33 | } -------------------------------------------------------------------------------- /mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Versioned.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mapper 24 | 25 | import java.time.LocalDateTime 26 | 27 | 28 | interface Versioned { 29 | fun nextVersion(old: T): T 30 | } 31 | 32 | interface VersionedWithTimestamp : Versioned { 33 | override fun nextVersion(old: LocalDateTime) = LocalDateTime.now() 34 | } 35 | 36 | interface VersionedWithInt : Versioned { 37 | override fun nextVersion(old: Int) = old + 1 38 | } 39 | 40 | interface VersionedWithLong : Versioned { 41 | override fun nextVersion(old: Long) = old + 1 42 | } 43 | -------------------------------------------------------------------------------- /core/src/main/kotlin/com/github/andrewoma/kwery/core/Cache.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core 24 | 25 | import java.util.concurrent.ConcurrentHashMap 26 | import java.util.concurrent.ConcurrentMap 27 | 28 | interface Cache { 29 | operator fun get(key: K): V? 30 | fun getOrPut(key: K, ifAbsent: (K) -> V): V 31 | } 32 | 33 | class ConcurrentHashMapCache : Cache { 34 | val underlying: ConcurrentMap = ConcurrentHashMap() 35 | 36 | override fun get(key: K) = underlying[key] 37 | override fun getOrPut(key: K, ifAbsent: (K) -> V): V = underlying.computeIfAbsent(key, ifAbsent) 38 | } -------------------------------------------------------------------------------- /fetcher/src/test/kotlin/com/github/andrewoma/kwery/fetcher/NodeTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.fetcher 24 | 25 | import org.junit.Test 26 | import kotlin.test.assertEquals 27 | 28 | class NodeTest { 29 | @Test fun testNodeParse() { 30 | fun assertParse(graph: String) { 31 | println("Parsing $graph") 32 | val node = Node.parse(graph) 33 | assertEquals(node.toString(), graph) 34 | } 35 | 36 | assertParse("hello") 37 | assertParse("hello, there") 38 | assertParse("hello, there(boy, girl(fire, truck), balls)") 39 | assertParse("hello(*), there(**)") 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /core/src/main/kotlin/com/github/andrewoma/kwery/core/builder/Filter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core.builder 24 | 25 | sealed class Filter { 26 | abstract fun countLeaves(): Int 27 | 28 | class Group(val operator: String = "and", val filters: MutableList = mutableListOf()): Filter() { 29 | override fun countLeaves(): Int { 30 | fun countLeaves(conditions: Group): Int = conditions.filters.fold(0) { count, c -> count + c.countLeaves() } 31 | return countLeaves(this) 32 | } 33 | } 34 | 35 | class Where(val where: String) : Filter() { 36 | override fun countLeaves(): Int = 1 37 | } 38 | } -------------------------------------------------------------------------------- /mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/util/CaseFormatters.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mapper.util 24 | 25 | val camelToLowerUnderscore: (String) -> String = { s -> 26 | if (s.isEmpty()) s else { 27 | val sb = StringBuilder(s.length + s.length / 3) 28 | 29 | sb.append(Character.toLowerCase(s[0])) 30 | for (ch in s.substring(1)) { 31 | sb.append(if (ch.isUpperCase()) "_" + Character.toLowerCase(ch) else ch) 32 | } 33 | sb.toString() 34 | } 35 | } 36 | 37 | val camelToTitle: (String) -> String = { s -> 38 | if (s.isEmpty()) s else { 39 | s.capitalize() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /core/src/test/kotlin/com/github/andrewoma/kwery/core/Datasources.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core 24 | 25 | import com.zaxxer.hikari.HikariDataSource 26 | 27 | val hsqlDataSource = HikariDataSource().apply { 28 | jdbcUrl = "jdbc:hsqldb:mem:kwery" 29 | } 30 | 31 | val postgresDataSource = HikariDataSource().apply { 32 | jdbcUrl = "jdbc:postgresql://localhost:5432/kwery" 33 | } 34 | 35 | val mysqlDataSource = HikariDataSource().apply { 36 | jdbcUrl = "jdbc:mysql://localhost:3306/kwery" 37 | username = "kwery" 38 | password = "kwery" 39 | } 40 | 41 | val sqliteDataSource = HikariDataSource().apply { 42 | jdbcUrl = "jdbc:sqlite::memory:" 43 | } 44 | -------------------------------------------------------------------------------- /mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/example/test/util/CaseFormattersTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mappertest.example.test.util 24 | 25 | import com.github.andrewoma.kwery.mapper.util.camelToLowerUnderscore 26 | import com.github.andrewoma.kwery.mapper.util.camelToTitle 27 | import org.junit.Test 28 | import kotlin.test.assertEquals 29 | 30 | class CaseFormattersTest { 31 | @Test fun `should convert to TitleCase`() { 32 | assertEquals("TitleCase", camelToTitle("titleCase")) 33 | } 34 | 35 | @Test fun `should convert to lower_underscore`() { 36 | assertEquals("lower_underscore", camelToLowerUnderscore("lowerUnderscore")) 37 | } 38 | } -------------------------------------------------------------------------------- /core/src/test/kotlin/com/github/andrewoma/kwery/core/SessionFactoryTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core 24 | 25 | import com.github.andrewoma.kwery.core.dialect.HsqlDialect 26 | import com.github.andrewoma.kwery.core.interceptor.LoggingInterceptor 27 | import org.junit.Test 28 | import kotlin.test.assertEquals 29 | 30 | class SessionFactoryTest { 31 | val factory = SessionFactory(hsqlDataSource, HsqlDialect(), LoggingInterceptor()) 32 | 33 | @Test fun `Use should acquire and release a connection`() { 34 | val name = factory.use { session -> 35 | session.select(dbNameSql) { it.string("name") }.single() 36 | } 37 | assertEquals("HSQLDB", name.trim()) 38 | } 39 | } -------------------------------------------------------------------------------- /example/src/main/kotlin/com/github/andrewoma/kwery/example/film/FilmConfiguration.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.example.film 24 | 25 | import com.fasterxml.jackson.annotation.JsonProperty 26 | import io.dropwizard.Configuration 27 | import io.dropwizard.db.DataSourceFactory 28 | import javax.validation.Valid 29 | import javax.validation.constraints.NotNull 30 | 31 | class FilmConfiguration : Configuration() { 32 | // TODO ... devise a more elegant solution. Tried Jackson's Kotlin module but 33 | // DropWizard requires a default constructor 34 | @Valid @NotNull @JsonProperty("database") 35 | private var _database: DataSourceFactory? = null 36 | val database: DataSourceFactory 37 | get() = _database!! 38 | } 39 | -------------------------------------------------------------------------------- /core/src/main/kotlin/com/github/andrewoma/kwery/core/interceptor/StatementInterceptor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core.interceptor 24 | 25 | import com.github.andrewoma.kwery.core.ExecutingStatement 26 | 27 | interface StatementInterceptor { 28 | fun construct(statement: ExecutingStatement): ExecutingStatement = statement 29 | 30 | fun preparing(statement: ExecutingStatement): ExecutingStatement = statement 31 | 32 | fun prepared(statement: ExecutingStatement): Unit { 33 | } 34 | 35 | fun executed(statement: ExecutingStatement): Unit { 36 | } 37 | 38 | fun closed(statement: ExecutingStatement): Unit { 39 | } 40 | 41 | fun exception(statement: ExecutingStatement, e: Exception): Exception = e 42 | } 43 | 44 | object noOpStatementInterceptor : StatementInterceptor 45 | -------------------------------------------------------------------------------- /example-simulations/src/test/scala/kwery/QuerySimulation.scala: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package kwery 24 | 25 | import io.gatling.core.Predef._ 26 | import io.gatling.http.Predef._ 27 | 28 | import scala.concurrent.duration._ 29 | 30 | class QuerySimulation extends Simulation { 31 | 32 | val httpConf = http 33 | .baseURL("http://localhost:9090/api") 34 | .acceptHeader("application/json") 35 | 36 | val scn = scenario("Query api") 37 | .exec(http("Film/Id").get("/films/3?log=none").check(status.is(200))) 38 | .exec(http("Film/Id+Actors").get("/films/5?fetch=actors&log=none").check(status.is(200))) 39 | 40 | setUp(scn.inject( 41 | rampUsersPerSec(1) to 200 during (30 seconds), 42 | constantUsersPerSec(200) during (3 minutes), 43 | rampUsersPerSec(200) to 1 during (30 seconds) 44 | ).protocols(httpConf)) 45 | } 46 | -------------------------------------------------------------------------------- /core/src/test/kotlin/com/github/andrewoma/kwery/core/ReadmeExamplesTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core 24 | 25 | import com.github.andrewoma.kwery.core.dialect.HsqlDialect 26 | import org.junit.Test 27 | 28 | class ReadmeExamplesTest : AbstractFilmSessionTest() { 29 | @Test fun readmeExamples() { 30 | val connection = hsqlDataSource.connection 31 | 32 | class Actor(val firstName: String, val lastName: String) 33 | 34 | val session = DefaultSession(connection, HsqlDialect()) 35 | 36 | val sql = "select * from actor where first_name = :first_name" 37 | 38 | val actors = session.select(sql, mapOf("first_name" to "Brad")) { row -> 39 | Actor(row.string("first_name"), row.string("last_name")) 40 | } 41 | 42 | println(actors.firstOrNull()?.firstName + actors.firstOrNull()?.lastName) 43 | } 44 | } -------------------------------------------------------------------------------- /example/src/main/kotlin/com/github/andrewoma/kwery/example/film/resources/Resource.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.example.film.resources 24 | 25 | import com.github.andrewoma.kwery.fetcher.GraphFetcher 26 | import com.github.andrewoma.kwery.fetcher.Node 27 | import com.github.andrewoma.kwery.mapper.Column 28 | 29 | interface Resource { 30 | val fetcher: GraphFetcher 31 | 32 | fun parameters(vararg parameters: Pair, Any?>): Map, Any?> { 33 | return parameters.toList().toMap().filter { it.value != null } 34 | } 35 | 36 | fun List.fetch(root: String?): List { 37 | if (root == null) return this 38 | return fetcher.fetch(this, Node.parse(root)) 39 | } 40 | 41 | fun T.fetch(root: String?): T { 42 | if (root == null) return this 43 | return fetcher.fetch(this, Node.parse(root)) 44 | } 45 | } -------------------------------------------------------------------------------- /core/src/test/kotlin/com/github/andrewoma/kwery/core/ManualTransactionTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core 24 | 25 | import org.junit.Test 26 | import kotlin.test.assertEquals 27 | 28 | class ManualTransactionTest : AbstractFilmSessionTest() { 29 | override var startTransactionByDefault: Boolean = false 30 | 31 | @Test fun `rollback should roll back transaction`() { 32 | val t = session.manualTransaction() 33 | val actor = insert(Actor("Kate", "Beckinsale")) 34 | assertEquals(1, selectActors(setOf(actor.id)).size) 35 | t.rollback() 36 | assertEquals(0, selectActors(setOf(actor.id)).size) 37 | } 38 | 39 | @Test fun `commit should commit transaction`() { 40 | val t = session.manualTransaction() 41 | val actor = insert(Actor("Kate", "Beckinsale")) 42 | assertEquals(1, selectActors(setOf(actor.id)).size) 43 | t.commit() 44 | assertEquals(1, selectActors(setOf(actor.id)).size) 45 | } 46 | } -------------------------------------------------------------------------------- /mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Defaults.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mapper 24 | 25 | import java.math.BigDecimal 26 | import java.sql.Date 27 | import java.sql.Time 28 | import java.sql.Timestamp 29 | import kotlin.reflect.KType 30 | import kotlin.reflect.full.defaultType 31 | 32 | val standardDefaults: Map = listOf( 33 | reifiedValue(true), 34 | reifiedValue(0.toByte()), 35 | reifiedValue(0.toChar()), 36 | reifiedValue(0.toShort()), 37 | reifiedValue(0), 38 | reifiedValue(0L), 39 | reifiedValue(0.toFloat()), 40 | reifiedValue(0.toDouble()), 41 | reifiedValue(BigDecimal(0)), 42 | reifiedValue(""), 43 | reifiedValue(Timestamp(0)), 44 | reifiedValue(Date(0)), 45 | reifiedValue(Time(0)), 46 | reifiedValue(ByteArray(0)) 47 | ).toMap() 48 | 49 | inline fun reifiedValue(default: T): Pair = T::class.defaultType to default 50 | 51 | -------------------------------------------------------------------------------- /publish.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | apply plugin: 'com.bmuschko.nexus' 24 | 25 | modifyPom { 26 | project { 27 | name 'kwery' 28 | description 'An SQL library for Kotlin' 29 | url 'https://github.com/andrewoma/kwery' 30 | packaging 'jar' 31 | 32 | licenses { 33 | license { 34 | name 'MIT License' 35 | url 'http://www.opensource.org/licenses/mit-license.php' 36 | distribution 'repo' 37 | } 38 | } 39 | 40 | scm { 41 | url 'https://github.com/andrewoma/kwery' 42 | connection 'scm:git:https://github.com/andrewoma/kwery.git' 43 | developerConnection 'scm:git:git@github.com:andrewoma/kwery.git' 44 | } 45 | 46 | developers { 47 | developer { 48 | id 'andrewoma' 49 | name "Andrew O'Malley" 50 | email 'andrewoma@gmail.com' 51 | url 'https://github.com/andrewoma' 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Column.kt: -------------------------------------------------------------------------------- 1 | package com.github.andrewoma.kwery.mapper 2 | 3 | /** 4 | * Column defines a how to map an SQL column to and from an object property of type `T` 5 | * within a containing class `C`. 6 | * 7 | * While columns can be added directly it is more common to use the `col` methods on `Table` 8 | * to provide sensible defaults. 9 | */ 10 | data class Column( 11 | /** 12 | * A function to extract the property value from the containing object 13 | */ 14 | val property: (C) -> T, 15 | 16 | /** 17 | * If a value is not `nullable` a default value must be provided to allow construction 18 | * of partially selected objects 19 | */ 20 | val defaultValue: T, 21 | 22 | /** 23 | * A converter between the SQL type and `T` 24 | */ 25 | val converter: Converter, 26 | 27 | /** 28 | * The name of the SQL column 29 | */ 30 | val name: String, 31 | 32 | /** 33 | * True if the column is part of the primary key 34 | */ 35 | val id: Boolean, 36 | 37 | /** 38 | * True if the column is used for versioning using optimistic locking 39 | */ 40 | val version: Boolean, 41 | 42 | /** 43 | * True if the column is selected in queries by default. 44 | * Generally true, but is useful to exclude `BLOBs` and `CLOBs` in some cases. 45 | */ 46 | val selectByDefault: Boolean, 47 | 48 | /** 49 | * True if the column is nullable 50 | */ 51 | val isNullable: Boolean 52 | ) { 53 | /** 54 | * A type-safe variant of `to` 55 | */ 56 | infix fun of(value: T): Pair, T> = Pair(this, value) 57 | 58 | /** 59 | * A type-safe variant of `to` with an optional value 60 | */ 61 | @Suppress("BASE_WITH_NULLABLE_UPPER_BOUND") 62 | infix fun optional(value: T?): Pair, T?> = Pair(this, value) 63 | 64 | override fun toString(): String { 65 | return "Column($name id=$id version=$version nullable=$isNullable)" // Prevent NPE in debugger on "property" 66 | } 67 | } -------------------------------------------------------------------------------- /example/src/main/kotlin/com/github/andrewoma/kwery/example/film/dao/LanguageDao.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.example.film.dao 24 | 25 | import com.github.andrewoma.kwery.core.Session 26 | import com.github.andrewoma.kwery.mapper.AbstractDao 27 | import com.github.andrewoma.kwery.mapper.Table 28 | import com.github.andrewoma.kwery.mapper.Value 29 | import com.github.andrewoma.kwery.mapper.VersionedWithInt 30 | import com.github.andrewoma.kwery.example.film.model.Language as L 31 | 32 | object languageTable : Table("language", tableConfig), VersionedWithInt { 33 | // @formatter:off 34 | val Id by col(L::id, id = true) 35 | val Name by col(L::name) 36 | val Version by col(L::version, version = true) 37 | // @formatter:on 38 | 39 | override fun idColumns(id: Int) = setOf(Id of id) 40 | override fun create(value: Value) = L(value.of(Id), value.of(Name), value.of(Version)) 41 | } 42 | 43 | class LanguageDao(session: Session) : AbstractDao(session, languageTable, { it.id }, "int", defaultId = 0) 44 | -------------------------------------------------------------------------------- /core/src/main/kotlin/com/github/andrewoma/kwery/core/SessionFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core 24 | 25 | import com.github.andrewoma.kwery.core.dialect.Dialect 26 | import com.github.andrewoma.kwery.core.interceptor.StatementInterceptor 27 | import com.github.andrewoma.kwery.core.interceptor.noOpStatementInterceptor 28 | import javax.sql.DataSource 29 | 30 | /** 31 | * SessionFactory provides sessions from a pooled DataSource 32 | */ 33 | class SessionFactory(val dataSource: DataSource, 34 | val dialect: Dialect, 35 | val interceptor: StatementInterceptor = noOpStatementInterceptor, 36 | val defaultOptions: StatementOptions = StatementOptions() 37 | 38 | ) { 39 | 40 | fun use(f: (Session) -> R): R { 41 | val connection = dataSource.connection 42 | try { 43 | val session = DefaultSession(connection, dialect, interceptor, defaultOptions) 44 | return f(session) 45 | } finally { 46 | connection.close() 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/example/LanguageDao.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mappertest.example 24 | 25 | import com.github.andrewoma.kwery.core.Session 26 | import com.github.andrewoma.kwery.mapper.AbstractDao 27 | import com.github.andrewoma.kwery.mapper.Table 28 | import com.github.andrewoma.kwery.mapper.Value 29 | import com.github.andrewoma.kwery.mapper.VersionedWithTimestamp 30 | import com.github.andrewoma.kwery.mappertest.example.Language as L 31 | 32 | object languageTable : Table("language", tableConfig), VersionedWithTimestamp { 33 | // @formatter:off 34 | val LanguageId by col(L::id, id = true) 35 | val Name by col(L::name) 36 | val LastUpdate by col(L::lastUpdate, version = true) 37 | // @formatter:on 38 | 39 | override fun idColumns(id: Int) = setOf(LanguageId of id) 40 | override fun create(value: Value) = L(value.of(LanguageId), value.of(Name), value.of(LastUpdate)) 41 | } 42 | 43 | class LanguageDao(session: Session) : AbstractDao(session, languageTable, { it.id }, "int", defaultId = -1) 44 | -------------------------------------------------------------------------------- /mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/example/test/LanguageDaoTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mappertest.example.test 24 | 25 | import com.github.andrewoma.kwery.mappertest.example.Language 26 | import com.github.andrewoma.kwery.mappertest.example.LanguageDao 27 | import java.time.LocalDateTime 28 | import kotlin.properties.Delegates 29 | 30 | class LanguageDaoTest : AbstractFilmDaoTest() { 31 | override var dao: LanguageDao by Delegates.notNull() 32 | 33 | override fun afterSessionSetup() { 34 | dao = LanguageDao(session) 35 | super.afterSessionSetup() 36 | } 37 | 38 | override val data = listOf( 39 | Language(-1, "German", LocalDateTime.now()), 40 | Language(-1, "Dutch", LocalDateTime.now()), 41 | Language(-1, "Japanese", LocalDateTime.now()), 42 | Language(-1, "Chinese", LocalDateTime.now()) 43 | ) 44 | 45 | override fun mutateContents(t: Language) = t.copy(name = "Russian") 46 | 47 | override fun contentsEqual(t1: Language, t2: Language) = 48 | t1.name == t2.name 49 | } -------------------------------------------------------------------------------- /transactional/src/main/kotlin/com/github/andrewoma/kwery/transactional/Transactional.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.transactional 24 | 25 | import com.github.andrewoma.kwery.core.defaultThreadLocalSessionName 26 | import java.lang.annotation.Inherited 27 | import kotlin.reflect.KClass 28 | 29 | @Inherited 30 | annotation class Transactional( 31 | /** 32 | * The name of the data source to use in the transaction 33 | */ 34 | val name: String = defaultThreadLocalSessionName, 35 | 36 | /** 37 | * A list of exceptions to rollback on. 38 | */ 39 | val rollbackOn: Array> = arrayOf(Exception::class), 40 | 41 | /** 42 | * A list of exceptions to *not* rollback on. Ignore exceptions take precedence over rollbackOn 43 | */ 44 | val ignore: Array> = arrayOf(), 45 | 46 | /** 47 | * If true, a session will be initialised but a transaction will not be started. 48 | * Transactions can be manually managed via the Session transaction functions. 49 | */ 50 | val manual: Boolean = false 51 | ) -------------------------------------------------------------------------------- /core/src/main/kotlin/com/github/andrewoma/kwery/core/dialect/MysqlDialect.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core.dialect 24 | 25 | import java.sql.* 26 | 27 | open class MysqlDialect : Dialect { 28 | 29 | override fun bind(value: Any, limit: Int) = when (value) { 30 | is String -> escapeSingleQuotedString(value.truncate(limit)) 31 | is Timestamp -> timestampFormat.get().format(value) 32 | is Date -> "'$value'" 33 | is Time -> "'$value'" 34 | is java.sql.Array -> throw UnsupportedOperationException() 35 | is Clob -> escapeSingleQuotedString(standardClob(value, limit)) 36 | is Blob -> standardBlob(value, limit) 37 | is ByteArray -> standardByteArray(value, limit) 38 | else -> value.toString() 39 | } 40 | 41 | override fun arrayBasedIn(name: String) = throw UnsupportedOperationException() 42 | 43 | override val supportsArrayBasedIn = false 44 | 45 | override val supportsAllocateIds = false 46 | 47 | override fun allocateIds(count: Int, sequence: String, columnName: String) = throw UnsupportedOperationException() 48 | 49 | override val supportsFetchingGeneratedKeysByName = false 50 | } 51 | -------------------------------------------------------------------------------- /core/src/main/kotlin/com/github/andrewoma/kwery/core/dialect/SqliteDialect.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core.dialect 24 | 25 | import java.sql.* 26 | 27 | open class SqliteDialect : Dialect { 28 | 29 | override fun bind(value: Any, limit: Int) = when (value) { 30 | is String -> escapeSingleQuotedString(value.truncate(limit)) 31 | is Timestamp -> timestampFormat.get().format(value) 32 | is Date -> "'$value 00:00:00.000'" 33 | is Time -> "'1970-01-01 $value.000'" 34 | is java.sql.Array -> throw UnsupportedOperationException() 35 | is Clob -> escapeSingleQuotedString(standardClob(value, limit)) 36 | is Blob -> standardBlob(value, limit) 37 | is ByteArray -> standardByteArray(value, limit) 38 | else -> value.toString() 39 | } 40 | 41 | override fun arrayBasedIn(name: String) = throw UnsupportedOperationException() 42 | 43 | override val supportsArrayBasedIn = false 44 | 45 | override val supportsAllocateIds = false 46 | 47 | override fun allocateIds(count: Int, sequence: String, columnName: String) = throw UnsupportedOperationException() 48 | 49 | override val supportsFetchingGeneratedKeysByName = false 50 | } 51 | -------------------------------------------------------------------------------- /mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Columns.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mapper 24 | 25 | import com.github.andrewoma.kwery.core.Row 26 | 27 | class PrefixedColumns(val prefix: String, table: Table, columns: Set>) { 28 | val mapper: (Row) -> T = table.rowMapper(columns, { "${prefix}__${it.name}" }) 29 | 30 | val optionalMapper: (Row) -> T? = { row -> 31 | val column = columns.firstOrNull { !it.isNullable } 32 | if (row.objectOrNull("${prefix}__${column!!.name}") == null) null else mapper(row) 33 | } 34 | 35 | val select: String = columns.asSequence().map { "$prefix.${it.name} ${prefix}__${it.name}" }.joinToString(", ") 36 | } 37 | 38 | fun Table.prefixed(prefix: String, columns: Set> = this.defaultColumns) = PrefixedColumns(prefix, this, columns) 39 | 40 | fun combine(mapper1: (Row) -> R1, mapper2: (Row) -> R2): (Row) -> Pair { 41 | return { mapper1(it) to mapper2(it) } 42 | } 43 | 44 | fun combine(mapper1: (Row) -> R1, mapper2: (Row) -> R2, mapper3: (Row) -> R3): (Row) -> Triple { 45 | return { Triple(mapper1(it), mapper2(it), mapper3(it)) } 46 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/github/andrewoma/kwery/core/dialect/HsqlDialect.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core.dialect 24 | 25 | import java.sql.* 26 | 27 | open class HsqlDialect : Dialect { 28 | 29 | override fun bind(value: Any, limit: Int): String = when (value) { 30 | is String -> escapeSingleQuotedString(value.truncate(limit)) 31 | is Timestamp -> timestampFormat.get().format(value) 32 | is Date -> "'$value'" 33 | is Time -> "'$value'" 34 | is java.sql.Array -> bindArray(value, limit, "array[", "]") 35 | is Blob -> standardBlob(value, limit) 36 | is ByteArray -> standardByteArray(value, limit) 37 | is Clob -> escapeSingleQuotedString(standardClob(value, limit)) 38 | else -> value.toString() 39 | } 40 | 41 | override val supportsArrayBasedIn = true 42 | 43 | override fun arrayBasedIn(name: String) = "in(unnest(:$name))" 44 | 45 | override val supportsAllocateIds = true 46 | 47 | override fun allocateIds(count: Int, sequence: String, columnName: String) = 48 | "select next value for $sequence as $columnName from unnest(sequence_array(1, $count, 1))" 49 | 50 | override val supportsFetchingGeneratedKeysByName = true 51 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/github/andrewoma/kwery/core/dialect/PostgresDialect.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core.dialect 24 | 25 | import java.sql.Date 26 | import java.sql.Time 27 | import java.sql.Timestamp 28 | import javax.xml.bind.DatatypeConverter 29 | 30 | open class PostgresDialect : Dialect { 31 | 32 | override fun bind(value: Any, limit: Int) = when (value) { 33 | is String -> escapeSingleQuotedString(value.truncate(limit)) 34 | is Timestamp -> timestampFormat.get().format(value) 35 | is Date -> "'$value'" 36 | is Time -> "'$value'" 37 | is java.sql.Array -> bindArray(value, limit, "array[", "]") 38 | is ByteArray -> "decode('${DatatypeConverter.printBase64Binary(value.truncate(limit))}','base64')" 39 | else -> value.toString() 40 | } 41 | 42 | override fun arrayBasedIn(name: String) = "= any(:$name)" 43 | 44 | override val supportsArrayBasedIn = true 45 | 46 | override val supportsAllocateIds = true 47 | 48 | override fun allocateIds(count: Int, sequence: String, columnName: String) = 49 | "select nextval('$sequence') as $columnName from generate_series(1, $count)" 50 | 51 | override val supportsFetchingGeneratedKeysByName = true 52 | } 53 | -------------------------------------------------------------------------------- /mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/example/Config.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mappertest.example 24 | 25 | import com.github.andrewoma.kwery.mapper.* 26 | import com.github.andrewoma.kwery.mapper.util.camelToLowerUnderscore 27 | import kotlin.reflect.KType 28 | 29 | val domainDefaults: Map = listOf( 30 | reifiedValue(FilmRating.G), 31 | reifiedValue(Language(-1)) 32 | ).toMap() 33 | 34 | val defaults: Map = standardDefaults + timeDefaults + domainDefaults 35 | 36 | val domainConverters: Map, Converter<*>> = listOf( 37 | reifiedConverter(filmRatingConverter), 38 | reifiedConverter(languageConverter) 39 | ).toMap() 40 | 41 | val converters: Map, Converter<*>> = standardConverters + timeConverters + domainConverters 42 | 43 | object languageConverter : SimpleConverter( 44 | { row, c -> Language(row.int(c)) }, 45 | { it.id } 46 | ) 47 | 48 | object filmRatingConverter : SimpleConverter( 49 | { row, c -> FilmRating.valueOf(row.string(c).replace('-', '_')) }, 50 | { it.name.replace('_', '-') } 51 | ) 52 | 53 | val tableConfig = TableConfiguration(defaults, converters, camelToLowerUnderscore) 54 | 55 | 56 | -------------------------------------------------------------------------------- /core/src/main/kotlin/com/github/andrewoma/kwery/core/interceptor/StatementInterceptorChain.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core.interceptor 24 | 25 | import com.github.andrewoma.kwery.core.ExecutingStatement 26 | 27 | class StatementInterceptorChain(val interceptors: List) : StatementInterceptor { 28 | override fun construct(statement: ExecutingStatement) = 29 | interceptors.fold(statement) { result, interceptor -> interceptor.construct(result) } 30 | 31 | override fun preparing(statement: ExecutingStatement) = 32 | interceptors.fold(statement) { result, interceptor -> interceptor.preparing(result) } 33 | 34 | override fun prepared(statement: ExecutingStatement) { 35 | interceptors.forEach { it.prepared(statement) } 36 | } 37 | 38 | override fun executed(statement: ExecutingStatement) { 39 | interceptors.forEach { it.executed(statement) } 40 | } 41 | 42 | override fun closed(statement: ExecutingStatement) { 43 | interceptors.forEach { it.closed(statement) } 44 | } 45 | 46 | override fun exception(statement: ExecutingStatement, e: Exception) = 47 | interceptors.fold(e) { result, interceptor -> interceptor.exception(statement, result) } 48 | } 49 | -------------------------------------------------------------------------------- /core/src/test/kotlin/com/github/andrewoma/kwery/core/ManagedThreadLocalSessionDelegationTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core 24 | 25 | import com.github.andrewoma.kwery.core.dialect.PostgresDialect 26 | import org.junit.Test 27 | import kotlin.test.assertNotNull 28 | import kotlin.test.assertNull 29 | 30 | class ManagedThreadLocalSessionDelegationTest : AbstractSessionDelegationTest() { 31 | companion object { 32 | val session = ManagedThreadLocalSession(postgresDataSource, PostgresDialect(), postgresLoggingInterceptor) 33 | } 34 | 35 | override fun withSession(f: (Session) -> R) = session.use(false) { f(session) } 36 | 37 | @Test fun `Manual transaction blocks should be honoured`() { 38 | withSession { session -> 39 | 40 | val t1 = session.manualTransaction() 41 | val key1 = session.insert(insertSql, mapOf("value" to "v1")) { it.int("key") } 42 | t1.commit() 43 | 44 | val t2 = session.manualTransaction() 45 | val key2 = session.insert(insertSql, mapOf("value" to "v2")) { it.int("key") } 46 | t2.rollback() 47 | 48 | assertNotNull(select(session, key1.second)) 49 | assertNull(select(session, key2.second)) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /example/src/main/kotlin/com/github/andrewoma/kwery/example/film/jersey/SqlExceptionMapper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.example.film.jersey 24 | 25 | import com.github.andrewoma.kwery.mapper.OptimisticLockException 26 | import io.dropwizard.jersey.errors.ErrorMessage 27 | import io.dropwizard.jersey.errors.LoggingExceptionMapper 28 | import java.sql.SQLException 29 | import java.sql.SQLIntegrityConstraintViolationException 30 | import javax.ws.rs.core.MediaType 31 | import javax.ws.rs.core.Response 32 | import javax.ws.rs.ext.Provider 33 | 34 | @Provider 35 | class SqlExceptionMapper : LoggingExceptionMapper() { 36 | 37 | override fun toResponse(exception: SQLException): Response { 38 | val response = super.toResponse(exception) // Logs exception 39 | 40 | val code = when (exception) { 41 | is SQLIntegrityConstraintViolationException -> Response.Status.CONFLICT.statusCode 42 | is OptimisticLockException -> 428 // http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#428 43 | else -> null 44 | } 45 | 46 | return if (code == null) response else Response.status(code) 47 | .type(MediaType.APPLICATION_JSON_TYPE) 48 | .entity(ErrorMessage(code, exception.message)).build() 49 | } 50 | } -------------------------------------------------------------------------------- /core/src/test/kotlin/com/github/andrewoma/kwery/core/SqliteDialectTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core 24 | 25 | import com.github.andrewoma.kwery.core.dialect.SqliteDialect 26 | import org.junit.Test 27 | import kotlin.test.assertEquals 28 | 29 | class SqliteDialectTest : AbstractDialectTest(sqliteDataSource, SqliteDialect()) { 30 | //language=MySQL 31 | override val sql = """ 32 | drop table if exists dialect_test; 33 | 34 | create table dialect_test ( 35 | id varchar(255), 36 | time_col time, 37 | date_col date, 38 | timestamp_col timestamp, 39 | binary_col blob, 40 | varchar_col varchar(1000), 41 | blob_col blob, 42 | clob_col text, 43 | array_col text -- Not supported 44 | ); 45 | 46 | drop table if exists test; 47 | 48 | create table test ( 49 | id varchar(255), 50 | value varchar(255) 51 | ) 52 | """ 53 | 54 | @Test fun `Limits should be applied to variable parameters`() { 55 | assertEquals("'12'", dialect.bind("12345", 2)) 56 | assertEquals("X'3132'", dialect.bind("12345".toByteArray(), 2)) 57 | } 58 | } -------------------------------------------------------------------------------- /mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/example/FilmActorDao.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mappertest.example 24 | 25 | import com.github.andrewoma.kwery.core.Session 26 | import com.github.andrewoma.kwery.mapper.* 27 | import com.github.andrewoma.kwery.mappertest.example.FilmActor as FA 28 | 29 | object filmActorTable : Table("film_actor", tableConfig), VersionedWithTimestamp { 30 | // @formatter:off 31 | val FilmId by col(FA.Id::filmId, path = { it.id }, id = true) 32 | val ActorId by col(FA.Id::actorId, path = { it.id }, id = true) 33 | val LastUpdate by col(FA::lastUpdate, version = true) 34 | // @formatter:on 35 | 36 | override fun idColumns(id: FA.Id) = setOf(FilmId of id.filmId, ActorId of id.actorId) 37 | override fun create(value: Value) = FA(FA.Id(value of FilmId, value of ActorId), value of LastUpdate) 38 | } 39 | 40 | class FilmActorDao(session: Session) : AbstractDao(session, filmActorTable, { it.id }, null, IdStrategy.Explicit) { 41 | 42 | fun findByFilmIds(ids: Collection): List { 43 | val name = "findByFilmIds" 44 | val sql = sql(name) { "select $columns from ${table.name} where film_id in (:ids)" } 45 | return session.select(sql, mapOf("ids" to ids), options(name), table.rowMapper()) 46 | } 47 | } 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/src/main/kotlin/com/github/andrewoma/kwery/example/film/dao/Config.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.example.film.dao 24 | 25 | import com.github.andrewoma.kwery.example.film.model.FilmRating 26 | import com.github.andrewoma.kwery.example.film.model.Language 27 | import com.github.andrewoma.kwery.mapper.* 28 | import com.github.andrewoma.kwery.mapper.util.camelToLowerUnderscore 29 | import kotlin.reflect.KType 30 | 31 | val domainDefaults: Map = listOf( 32 | reifiedValue(FilmRating.G), 33 | reifiedValue(Language(-1)) 34 | ).toMap() 35 | 36 | val defaults: Map = standardDefaults + timeDefaults + domainDefaults 37 | 38 | val domainConverters: Map, Converter<*>> = listOf( 39 | reifiedConverter(filmRatingConverter), 40 | reifiedConverter(languageConverter) 41 | ).toMap() 42 | 43 | val converters: Map, Converter<*>> = standardConverters + timeConverters + domainConverters 44 | 45 | object languageConverter : SimpleConverter( 46 | { row, c -> Language(row.int(c)) }, 47 | { it.id } 48 | ) 49 | 50 | object filmRatingConverter : SimpleConverter( 51 | { row, c -> FilmRating.valueOf(row.string(c).replace('-', '_')) }, 52 | { it.name.replace('_', '-') } 53 | ) 54 | 55 | val tableConfig = TableConfiguration(defaults, converters, camelToLowerUnderscore) 56 | -------------------------------------------------------------------------------- /mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/example/test/FilmActorDaoTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mappertest.example.test 24 | 25 | import com.github.andrewoma.kwery.mappertest.example.FilmActor 26 | import com.github.andrewoma.kwery.mappertest.example.FilmActorDao 27 | import java.time.LocalDateTime 28 | import kotlin.properties.Delegates 29 | 30 | class FilmActorDaoTest : AbstractFilmDaoTest() { 31 | override var dao: FilmActorDao by Delegates.notNull() 32 | 33 | override fun afterSessionSetup() { 34 | dao = FilmActorDao(session) 35 | super.afterSessionSetup() 36 | dao.session.update("delete from ${dao.table.name}") 37 | } 38 | 39 | override val data by lazy(LazyThreadSafetyMode.NONE) { 40 | listOf( 41 | FilmActor(FilmActor.Id(sd.actorKate.id, sd.filmUnderworld.id), LocalDateTime.now()), 42 | FilmActor(FilmActor.Id(sd.actorKate.id, sd.filmUnderworld2.id), LocalDateTime.now()), 43 | FilmActor(FilmActor.Id(sd.actorBrad.id, sd.filmUnderworld.id), LocalDateTime.now()), 44 | FilmActor(FilmActor.Id(sd.actorBrad.id, sd.filmUnderworld2.id), LocalDateTime.now()) 45 | ) 46 | } 47 | 48 | override fun mutateContents(t: FilmActor) = t 49 | 50 | override fun contentsEqual(t1: FilmActor, t2: FilmActor) = true 51 | } -------------------------------------------------------------------------------- /mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/example/FimGraphFetcher.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mappertest.example 24 | 25 | import com.github.andrewoma.kwery.core.Session 26 | import com.github.andrewoma.kwery.fetcher.CollectionProperty 27 | import com.github.andrewoma.kwery.fetcher.GraphFetcher 28 | import com.github.andrewoma.kwery.fetcher.Property 29 | import com.github.andrewoma.kwery.fetcher.Type 30 | 31 | fun createFilmFetcher(session: Session): GraphFetcher { 32 | val languageDao = LanguageDao(session) 33 | val filmActorDao = FilmActorDao(session) 34 | val actorDao = ActorDao(session, filmActorDao) 35 | val filmDao = FilmDao(session) 36 | 37 | val language = Type(Language::id, { languageDao.findByIds(it) }) 38 | val actor = Type(Actor::id, { actorDao.findByIds(it) }) 39 | 40 | val film = Type(Film::id, { filmDao.findByIds(it) }, listOf( 41 | Property(Film::language, language, { it.language.id }, { f, l -> f.copy(language = l) }), 42 | Property(Film::originalLanguage, language, { it.originalLanguage?.id }, { f, l -> f.copy(originalLanguage = l) }), 43 | CollectionProperty(Film::actors, actor, { it.id }, 44 | { f, a -> f.copy(actors = a.toSet()) }, 45 | { actorDao.findByFilmIds(it) }) 46 | )) 47 | 48 | return GraphFetcher(setOf(language, actor, film)) 49 | } 50 | 51 | -------------------------------------------------------------------------------- /core/src/test/kotlin/com/github/andrewoma/kwery/core/MysqlDialectTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core 24 | 25 | import com.github.andrewoma.kwery.core.dialect.MysqlDialect 26 | import org.junit.Test 27 | import kotlin.test.assertEquals 28 | 29 | class MysqlDialectTest : AbstractDialectTest(mysqlDataSource, MysqlDialect()) { 30 | //language=MySQL 31 | override val sql = """ 32 | drop table if exists dialect_test; 33 | 34 | create table dialect_test ( 35 | id varchar(255), 36 | time_col time, 37 | date_col date, 38 | timestamp_col timestamp, 39 | -- timestamp_col timestamp(3), Waiting on travis to support this 40 | binary_col blob, 41 | varchar_col varchar(1000), 42 | blob_col blob, 43 | clob_col text, 44 | array_col text -- Not supported 45 | ) engine innodb char set utf8; 46 | 47 | drop table if exists test; 48 | 49 | create table test ( 50 | id varchar(255), 51 | value varchar(255) 52 | ) engine innodb char set utf8 53 | """ 54 | 55 | @Test fun `Limits should be applied to variable parameters`() { 56 | assertEquals("'12'", dialect.bind("12345", 2)) 57 | assertEquals("X'3132'", dialect.bind("12345".toByteArray(), 2)) 58 | } 59 | } -------------------------------------------------------------------------------- /example-simulations/src/test/resources/recorder.conf: -------------------------------------------------------------------------------- 1 | recorder { 2 | core { 3 | #encoding = "utf-8" # The encoding used for reading/writing request bodies and the generated simulation 4 | #outputFolder = "" # The folder where generated simulation will we written 5 | #package = "" # The package's name of the generated simulation 6 | #className = "RecordedSimulation" # The name of the generated Simulation class 7 | #thresholdForPauseCreation = 100 # The minimum time, in milliseconds, that must pass between requests to trigger a pause creation 8 | #saveConfig = false # When set to true, the configuration from the Recorder GUI overwrites this configuration 9 | } 10 | filters { 11 | #filterStrategy = "Disabled" # The selected filter resources filter strategy (currently supported : "Disabled", "BlackList", "WhiteList") 12 | #whitelist = [] # The list of ressources patterns that are part of the Recorder's whitelist 13 | #blacklist = [] # The list of ressources patterns that are part of the Recorder's blacklist 14 | } 15 | http { 16 | #automaticReferer = true # When set to false, write the referer + enable 'disableAutoReferer' in the generated simulation 17 | #followRedirect = true # When set to false, write redirect requests + enable 'disableFollowRedirect' in the generated simulation 18 | #removeConditionalCache = true # When set to true, removes from the generated requests headers leading to request caching 19 | #inferHtmlResources = true # When set to true, add inferred resources + set 'inferHtmlResources' with the configured blacklist/whitelist in the generated simulation 20 | } 21 | proxy { 22 | #port = 8000 # Local port used by Gatling's Proxy for HTTP/HTTPS 23 | outgoing { 24 | #host = "" # The outgoing proxy's hostname 25 | #username = "" # The username to use to connect to the outgoing proxy 26 | #password = "" # The password corresponding to the user to use to connect to the outgoing proxy 27 | #port = 0 # The HTTP port to use to connect to the outgoing proxy 28 | #sslPort = 0 # If set, The HTTPS port to use to connect to the outgoing proxy 29 | } 30 | } 31 | netty { 32 | #maxInitialLineLength = 10000 # Maximum length of the initial line of the response (e.g. "HTTP/1.0 200 OK") 33 | #maxHeaderSize = 20000 # Maximum size, in bytes, of each request's headers 34 | #maxChunkSize = 8192 # Maximum length of the content or each chunk 35 | #maxContentLength = 100000000 # Maximum length of the aggregated content of each response 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /gradle/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /example/src/main/kotlin/com/github/andrewoma/kwery/example/film/resources/ActorResource.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.example.film.resources 24 | 25 | import com.codahale.metrics.annotation.Timed 26 | import com.github.andrewoma.kwery.example.film.dao.ActorDao 27 | import com.github.andrewoma.kwery.example.film.dao.actorTable 28 | import com.github.andrewoma.kwery.example.film.model.Actor 29 | import com.github.andrewoma.kwery.fetcher.GraphFetcher 30 | import com.github.andrewoma.kwery.transactional.jersey.Transactional 31 | import javax.ws.rs.* 32 | import javax.ws.rs.core.MediaType 33 | 34 | @Path("/actors") 35 | @Produces(MediaType.APPLICATION_JSON) 36 | @Transactional class ActorResource(val actorDao: ActorDao, override val fetcher: GraphFetcher) : Resource { 37 | 38 | @Timed @GET 39 | fun find(@QueryParam("firstName") firstName: String?, 40 | @QueryParam("lastName") lastName: String?, 41 | @QueryParam("fetch") root: String?): List { 42 | 43 | val filter = parameters( 44 | actorTable.FirstName optional firstName, 45 | actorTable.LastName optional lastName 46 | ) 47 | 48 | return actorDao.findByExample(actorTable.copy(Actor(), filter), filter.keys).fetch(root) 49 | } 50 | 51 | @Timed @GET @Path("/{id}") 52 | fun findById(@PathParam("id") id: Int, @QueryParam("fetch") root: String?): Actor { 53 | return actorDao.findById(id).fetch(root) ?: throw NotFoundException("$id not found") 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/example/Model.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mappertest.example 24 | 25 | import java.time.Duration 26 | import java.time.LocalDateTime 27 | 28 | private val defaultLocalDateTime = LocalDateTime.now() 29 | 30 | data class Name(val firstName: String, val lastName: String) 31 | 32 | data class Actor(val name: Name, val id: Int = -1, val lastUpdate: LocalDateTime = defaultLocalDateTime) 33 | 34 | fun Actor(id: Int = -1) = Actor(Name("", ""), id, defaultLocalDateTime) 35 | 36 | enum class FilmRating { 37 | G, PG, PG_13, R, NC_17 38 | } 39 | 40 | data class Film( 41 | val id: Int, 42 | val title: String, 43 | val releaseYear: Int?, 44 | val language: Language, 45 | val originalLanguage: Language?, 46 | val duration: Duration?, // minutes 47 | val rating: FilmRating?, 48 | val lastUpdate: LocalDateTime = LocalDateTime.now(), 49 | val specialFeatures: List = listOf(), 50 | val actors: Set = setOf() 51 | ) 52 | 53 | fun Film(id: Int = -1): Film = Film(id, "", 0, Language(-1), Language(-1), Duration.ZERO, 54 | FilmRating.G, defaultLocalDateTime) 55 | 56 | data class FilmActor( 57 | val id: FilmActor.Id, 58 | val lastUpdate: LocalDateTime 59 | 60 | ) { 61 | data class Id(val filmId: Int, val actorId: Int) 62 | } 63 | 64 | data class Language(val id: Int, val name: String, val lastUpdate: LocalDateTime) 65 | 66 | fun Language(id: Int = -1): Language = Language(id, "", defaultLocalDateTime) 67 | -------------------------------------------------------------------------------- /core/src/test/kotlin/com/github/andrewoma/kwery/core/PostgresDialectTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core 24 | 25 | import com.github.andrewoma.kwery.core.dialect.PostgresDialect 26 | import org.junit.Test 27 | import kotlin.test.assertEquals 28 | 29 | class PostgresDialectTest : AbstractDialectTest(postgresDataSource, PostgresDialect()) { 30 | //language=PostgreSQL 31 | override val sql = """ 32 | drop table if exists dialect_test; 33 | 34 | create table dialect_test ( 35 | id varchar(255), 36 | time_col time, 37 | date_col date, 38 | timestamp_col timestamp, 39 | binary_col bytea, 40 | varchar_col varchar(1000), 41 | blob_col oid, 42 | clob_col text, -- Postgres doesn't have a CLOB type 43 | array_col int array 44 | ); 45 | 46 | drop table if exists test; 47 | 48 | create table test ( 49 | id varchar(255), 50 | value varchar(255) 51 | ); 52 | 53 | drop sequence if exists test_seq; 54 | create sequence test_seq; 55 | """ 56 | 57 | @Test fun `Limits should be applied to variable parameters`() { 58 | assertEquals("'12'", dialect.bind("12345", 2)) 59 | assertEquals("decode('MTI=','base64')", dialect.bind("12345".toByteArray(), 2)) 60 | 61 | val array = session.connection.createArrayOf("varchar", arrayOf("12345")) 62 | assertEquals("array['12']", dialect.bind(array, 2)) 63 | } 64 | } -------------------------------------------------------------------------------- /example/src/main/kotlin/com/github/andrewoma/kwery/example/film/dao/ActorDao.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.example.film.dao 24 | 25 | import com.github.andrewoma.kwery.core.Session 26 | import com.github.andrewoma.kwery.example.film.model.Actor 27 | import com.github.andrewoma.kwery.example.film.model.FilmActor 28 | import com.github.andrewoma.kwery.mapper.AbstractDao 29 | import com.github.andrewoma.kwery.mapper.Table 30 | import com.github.andrewoma.kwery.mapper.Value 31 | import com.github.andrewoma.kwery.mapper.VersionedWithInt 32 | import com.github.andrewoma.kwery.example.film.model.Actor as A 33 | import com.github.andrewoma.kwery.example.film.model.Name as N 34 | 35 | 36 | object actorTable : Table("actor", tableConfig, "actor_seq"), VersionedWithInt { 37 | // @formatter:off 38 | val Id by col(A::id, id = true) 39 | val FirstName by col(N::first, { it.name }) 40 | val LastName by col(N::last, { it.name }) 41 | val Version by col(A::version, version = true) 42 | // @formatter:on 43 | 44 | override fun idColumns(id: Int) = setOf(Id of id) 45 | 46 | override fun create(value: Value) = A(value of Id, N(value of FirstName, value of LastName), 47 | value.of(Version)) 48 | } 49 | 50 | class ActorDao(session: Session) : AbstractDao(session, actorTable, { it.id }, "int", defaultId = 0) { 51 | 52 | fun findByFilmIds(filmActors: Collection): Map> { 53 | val films = findByIds(filmActors.map { it.id.actorId }.toSet()) 54 | return filmActors.groupBy { it.id.filmId }.mapValues { it.value.map { films[it.id.actorId]!! } } 55 | } 56 | } -------------------------------------------------------------------------------- /example/src/main/kotlin/com/github/andrewoma/kwery/example/film/resources/FilmResource.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.example.film.resources 24 | 25 | import com.codahale.metrics.annotation.Timed 26 | import com.github.andrewoma.kwery.example.film.dao.FilmDao 27 | import com.github.andrewoma.kwery.example.film.dao.filmTable 28 | import com.github.andrewoma.kwery.example.film.model.Film 29 | import com.github.andrewoma.kwery.example.film.model.FilmRating 30 | import com.github.andrewoma.kwery.fetcher.GraphFetcher 31 | import com.github.andrewoma.kwery.transactional.jersey.Transactional 32 | import javax.ws.rs.* 33 | import javax.ws.rs.core.MediaType 34 | 35 | 36 | @Path("/films") 37 | @Produces(MediaType.APPLICATION_JSON) 38 | @Transactional class FilmResource(val filmDao: FilmDao, override val fetcher: GraphFetcher) : Resource { 39 | @Timed @GET 40 | fun find(@QueryParam("title") title: String?, 41 | @QueryParam("releaseYear") releaseYear: Int?, 42 | @QueryParam("rating") rating: FilmRating?, 43 | @QueryParam("fetch") root: String?): List { 44 | 45 | val filter = parameters( 46 | filmTable.Title optional title, 47 | filmTable.ReleaseYear optional releaseYear, 48 | filmTable.Rating optional rating 49 | ) 50 | 51 | return filmDao.findByExample(filmTable.copy(Film(), filter), filter.keys).fetch(root) 52 | } 53 | 54 | @Timed @GET @Path("/{id}") 55 | fun findById(@PathParam("id") id: Int, @QueryParam("fetch") root: String?): Film { 56 | return filmDao.findById(id).fetch(root) ?: throw NotFoundException("$id not found") 57 | } 58 | } -------------------------------------------------------------------------------- /transactional/README.md: -------------------------------------------------------------------------------- 1 | The transactional module provides general purpose transactional interceptors for kwery. 2 | 3 | Note: the use of this module is discouraged as it may be removed - using a `ThreadLocalSession` with 4 | [transaction blocks](../core#transactions) is preferred as they work without interceptor 5 | configuration. 6 | 7 | Transaction interceptors are useful to automatically start transactions for method calls, 8 | rolling back on exceptions. 9 | 10 | The key advantage of interceptors is they allow services to be composed and nested in 11 | different combinations while implicitly managing transactions at the outer service layer. 12 | 13 | #### Usage 14 | 15 | Transactional interceptors are created by the `transactionalFactory` object. 16 | `transactionalFactory` supports any object annotated by `transactional` that uses 17 | `ManagedThreadLocalSessions` internally. e.g. 18 | 19 | ```kotlin 20 | @Transactional open class MyService(val session: Session) { 21 | open fun foo() {} 22 | } 23 | 24 | val session = ManagedThreadLocalSession(dataSource, HsqlDialect(), LoggingInterceptor()) 25 | val service = transactionalFactory.fromClass(MyService(session), MyService::session) 26 | 27 | // Now calls to service automatically occur within a transaction 28 | service.foo() 29 | ``` 30 | 31 | When creating a transactional proxy from a concrete class (as shown above) it is necessary 32 | to make the class and functions it defines `open` to permit the proxy to subclass. A default 33 | constructor or constructor arguments are also required. 34 | 35 | These restrictions aren't required if the object being proxied has an interface. 36 | 37 | #### Configuration 38 | 39 | By default, any exception (including checked) results in the transaction rolling back. 40 | 41 | However the `rollbackOn` and `ignore` parameters can be used to control the behaviour. 42 | e.g. The following will only rollback on instances of `RuntimeException` excluding `IllegalArgumentException`. 43 | 44 | ```kotlin 45 | @Transactional(rollbackOn = arrayOf(RuntimeException::class), ignore = arrayOf(IllegalArgumentException::class)) 46 | open class MyService(val session: Session) { 47 | open fun foo() {} 48 | } 49 | ``` 50 | 51 | Transactional annotations can be applied at either the class or method level. If applied at the method 52 | level, they override the class level annotations. 53 | 54 | Sometimes it's convenient to manually manage transactions. This can be done via the `manual` parameter. e.g. 55 | 56 | ```kotlin 57 | open class MyService(val session: Session) { 58 | @Transactional(manual = true) open fun foo() { 59 | // Use Session's transaction functions ... 60 | session.transaction { 61 | } 62 | } 63 | } 64 | ``` 65 | 66 | Finally, multiple datasources can be supported via the `name` attribute (name underlying `ManagedThreadLocalSessions` to match). -------------------------------------------------------------------------------- /core/src/test/kotlin/com/github/andrewoma/kwery/core/TransactionBlockTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core 24 | 25 | import org.junit.Test 26 | import kotlin.test.assertEquals 27 | 28 | class TransactionBlockTest : AbstractFilmSessionTest() { 29 | override var startTransactionByDefault: Boolean = false 30 | 31 | @Test fun `setRollbackOnly should roll back transaction`() { 32 | val actor = session.transaction { t -> 33 | val inserted = insert(Actor("Kate", "Beckinsale")) 34 | t.rollbackOnly = true 35 | assertEquals(1, selectActors(setOf(inserted.id)).size) 36 | inserted 37 | } 38 | assertEquals(0, selectActors(setOf(actor.id)).size) 39 | } 40 | 41 | @Test fun `exception should roll back transaction`() { 42 | var id: Int? = null 43 | try { 44 | session.transaction { _ -> 45 | val actor = insert(Actor("Kate", "Beckinsale")) 46 | id = actor.id 47 | assertEquals(1, selectActors(setOf(actor.id)).size) 48 | throw RuntimeException("From block") 49 | } 50 | } catch(e: Exception) { 51 | assertEquals("From block", e.message) 52 | } 53 | assertEquals(0, selectActors(setOf(id)).size) 54 | } 55 | 56 | @Test fun `block should be implicitly committed`() { 57 | val actor = session.transaction { _ -> 58 | val inserted = insert(Actor("Kate", "Beckinsale")) 59 | assertEquals(1, selectActors(setOf(inserted.id)).size) 60 | inserted 61 | } 62 | assertEquals(1, selectActors(setOf(actor.id)).size) 63 | } 64 | } 65 | 66 | -------------------------------------------------------------------------------- /core/src/test/kotlin/com/github/andrewoma/kwery/core/BatchUpdateTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core 24 | 25 | import org.junit.Test 26 | import kotlin.test.assertEquals 27 | import kotlin.test.assertNotNull 28 | 29 | class BatchUpdateTest : AbstractFilmSessionTest() { 30 | 31 | @Test fun `insert rows in a batch`() { 32 | var nextId = 10000 33 | session.update("delete from actor where actor_id >= $nextId") 34 | 35 | val actors = listOf( 36 | Actor("Andrew", "O'Malley", ++nextId), 37 | Actor("Bill", "Murray", ++nextId) 38 | ) 39 | 40 | val sql = "insert into actor(actor_id, first_name, last_name, last_update) " + 41 | "values (:actor_id, :first_name, :last_name, :last_update)" 42 | 43 | val list = session.batchUpdate(sql, actors.map { it.toMap() }) 44 | assertEquals(listOf(1, 1), list) 45 | 46 | val found = selectActors(setOf(nextId, --nextId)) 47 | assertEquals(actors, found) 48 | } 49 | 50 | @Test fun `insert rows in a batch with generated keys`() { 51 | val actors = listOf( 52 | Actor("Andrew", "O'Malley", 0), 53 | Actor("Bill", "Murray", 0), 54 | Actor("Ted", "Murray", 0) 55 | ) 56 | 57 | val sql = "insert into actor(first_name, last_name, last_update) " + 58 | "values (:first_name, :last_name, :last_update)" 59 | 60 | val list = session.batchInsert(sql, actors.map { it.toMap() }) { row -> row.resultSet.getInt(1) } 61 | assertEquals(3, list.size) 62 | list.forEach { 63 | assertEquals(1, it.first) 64 | assertNotNull(it.second) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /core/src/test/kotlin/com/github/andrewoma/kwery/core/HsqlDialectTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core 24 | 25 | import com.github.andrewoma.kwery.core.dialect.HsqlDialect 26 | import org.junit.Test 27 | import kotlin.test.assertEquals 28 | 29 | class HsqlDialectTest : AbstractDialectTest(hsqlDataSource, HsqlDialect()) { 30 | //language=SQL 31 | override val sql = """ 32 | create table dialect_test ( 33 | id varchar(255), 34 | time_col time, 35 | date_col date, 36 | timestamp_col timestamp, 37 | binary_col binary(6), 38 | varchar_col varchar(1000), 39 | blob_col blob, 40 | clob_col clob, 41 | array_col int array 42 | ); 43 | 44 | create table test ( 45 | id varchar(255), 46 | value varchar(255) 47 | ); 48 | 49 | create sequence test_seq 50 | """ 51 | 52 | @Test fun `Limits should be applied to variable parameters`() { 53 | assertEquals("'12'", dialect.bind("12345", 2)) 54 | assertEquals("X'3132'", dialect.bind("12345".toByteArray(), 2)) 55 | 56 | val clob = session.connection.createClob() 57 | clob.setString(1, "12345") 58 | assertEquals("'12'", dialect.bind(clob, 2)) 59 | 60 | val blob = session.connection.createBlob() 61 | blob.setBytes(1, "12345".toByteArray()) 62 | assertEquals("X'3132'", dialect.bind(blob, 2)) 63 | 64 | val array = session.connection.createArrayOf("varchar", arrayOf("12345")) 65 | assertEquals("array['12']", dialect.bind(array, 2)) 66 | } 67 | } -------------------------------------------------------------------------------- /core/src/test/kotlin/com/github/andrewoma/kwery/core/ReentrantTransactionTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core 24 | 25 | import org.junit.Test 26 | import kotlin.test.assertEquals 27 | 28 | class ReentrantTransactionTest : AbstractFilmSessionTest() { 29 | override var startTransactionByDefault: Boolean = false 30 | 31 | override fun afterSessionSetup() { 32 | super.afterSessionSetup() 33 | session.update("delete from actor") 34 | } 35 | 36 | fun insertOneThenFail(name: String) = session.transaction { t -> 37 | insert(Actor(name, "$name 1")) 38 | t.rollbackOnly = true 39 | } 40 | 41 | fun insertMultiple(name: String) = session.transaction { 42 | insert(Actor(name, "$name 2")) 43 | insert(Actor(name, "$name 3")) 44 | } 45 | 46 | @Test fun `should support separate transactions`() { 47 | insertMultiple("Bill") 48 | insertMultiple("Ben") 49 | assertEquals(4, countActors()) 50 | } 51 | 52 | @Test fun `should support reentrant`() { 53 | session.transaction { 54 | insertMultiple("Bill") 55 | insertMultiple("Ben") 56 | } 57 | assertEquals(4, countActors()) 58 | } 59 | 60 | @Test fun `should rollback all if inner transaction fails`() { 61 | session.transaction { 62 | insertMultiple("Bill") 63 | insertOneThenFail("Ben") 64 | } 65 | assertEquals(0, countActors()) 66 | } 67 | 68 | @Test fun `should rollback only failed transaction`() { 69 | insertMultiple("Bill") 70 | insertOneThenFail("Ben") 71 | assertEquals(2, countActors()) 72 | } 73 | 74 | private fun countActors() = session.select("select count(*) c from actor") { it.int("c") }.single() 75 | } -------------------------------------------------------------------------------- /example/src/main/kotlin/com/github/andrewoma/kwery/example/film/dao/FilmActorDao.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.example.film.dao 24 | 25 | import com.github.andrewoma.kwery.core.Session 26 | import com.github.andrewoma.kwery.example.film.model.FilmActor 27 | import com.github.andrewoma.kwery.mapper.* 28 | import com.github.andrewoma.kwery.example.film.model.FilmActor as FA 29 | 30 | 31 | object filmActorTable : Table("film_actor", tableConfig), VersionedWithTimestamp { 32 | // @formatter:off 33 | val FilmId by col(FA.Id::filmId, path = { it.id }, id = true) 34 | val ActorId by col(FA.Id::actorId, path = { it.id }, id = true) 35 | // @formatter:on 36 | 37 | override fun idColumns(id: FA.Id) = setOf(FilmId of id.filmId, ActorId of id.actorId) 38 | override fun create(value: Value) = FA(FA.Id(value of FilmId, value of ActorId)) 39 | } 40 | 41 | class FilmActorDao(session: Session) : AbstractDao(session, filmActorTable, { it.id }, null, IdStrategy.Explicit) { 42 | 43 | fun findByFilmIds(ids: Collection): List { 44 | val name = "findByFilmIds" 45 | val sql = sql(name) { "select $columns from ${table.name} where film_id in(unnest(:ids))" } 46 | val idsArray = session.connection.createArrayOf("int", ids.toTypedArray()) 47 | return session.select(sql, mapOf("ids" to idsArray), options(name), table.rowMapper()) 48 | } 49 | 50 | fun findByActorIds(ids: Collection): List { 51 | val name = "findByActorIds" 52 | val sql = sql(name) { "select $columns from ${table.name} where actor_id in(unnest(:ids))" } 53 | val idsArray = session.connection.createArrayOf("int", ids.toTypedArray()) 54 | return session.select(sql, mapOf("ids" to idsArray), options(name), table.rowMapper()) 55 | } 56 | } -------------------------------------------------------------------------------- /example/src/main/kotlin/com/github/andrewoma/kwery/example/film/model/FilmModel.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.example.film.model 24 | 25 | import java.time.Duration 26 | 27 | data class Name(val first: String = "", val last: String = "") 28 | 29 | data class Actor( 30 | val id: Int = 0, 31 | val name: Name = Name(), 32 | override val version: Int = 0, 33 | val films: Set = setOf() 34 | ) : AttributeSetByVersion 35 | 36 | data class Film( 37 | val id: Int = 0, 38 | val title: String = "", 39 | val description: String = "", 40 | val releaseYear: Int = 0, 41 | val language: Language = Language(0), 42 | val originalLanguage: Language? = null, 43 | val duration: Duration? = null, // minutes 44 | val rating: FilmRating? = null, 45 | val specialFeatures: List = listOf(), 46 | val actors: Set = setOf(), 47 | override val version: Int = 0 48 | 49 | ) : AttributeSetByVersion 50 | 51 | enum class FilmRating { 52 | G, PG, PG_13, R, NC_17 53 | } 54 | 55 | data class FilmActor(val id: FilmActor.Id = FilmActor.Id()) { 56 | data class Id(val filmId: Int = 0, val actorId: Int = 0) 57 | } 58 | 59 | data class Language( 60 | val id: Int = 0, 61 | val name: String = "", 62 | override val version: Int = 0 63 | ) : AttributeSetByVersion 64 | 65 | interface Version { 66 | val version: Int 67 | } 68 | 69 | // TODO ... move AttributeSet logic to be external to the model itself into the Jackson filter definition 70 | enum class AttributeSet { Id, All } 71 | 72 | interface HasAttributeSet { 73 | fun attributeSet(): AttributeSet 74 | } 75 | 76 | interface AttributeSetByVersion : HasAttributeSet, Version { 77 | override fun attributeSet() = if (this.version == 0) AttributeSet.Id else AttributeSet.All 78 | } 79 | -------------------------------------------------------------------------------- /mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/Dao.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mapper 24 | 25 | 26 | interface Dao { 27 | val defaultColumns: Set> 28 | val defaultIdStrategy: IdStrategy 29 | 30 | fun findById(id: ID, columns: Set> = defaultColumns): T? 31 | 32 | /** 33 | * Selects the row using `SELECT ... FOR UPDATE` acquiring a pessimistic lock. 34 | * In general, using optimistic locking via versioning is preferred and it is 35 | * safe from deadlocks and allows safe updates to extend to external updates such as via a UI. 36 | * However, pessimistic locks are occasionally required to ensure integrity when performing 37 | * find followed by an update, e.g. updating an account balance. 38 | * If acquiring multiple rows for update via this method, try to order the acquisition of locks 39 | * by entity and ids within entity to avoid deadlocks. 40 | */ 41 | fun findByIdForUpdate(id: ID, columns: Set> = defaultColumns): T? 42 | 43 | fun findByIds(ids: Collection, columns: Set> = defaultColumns): Map 44 | 45 | fun findAll(columns: Set> = defaultColumns): List 46 | 47 | fun findByExample(example: T, exampleColumns: Set>, columns: Set> = defaultColumns): List 48 | 49 | fun update(oldValue: T, newValue: T, deltaOnly: Boolean = false): T 50 | 51 | fun delete(id: ID): Int 52 | 53 | fun unsafeUpdate(newValue: T): T 54 | 55 | fun insert(value: T, idStrategy: IdStrategy = defaultIdStrategy): T 56 | 57 | fun batchInsert(values: List, idStrategy: IdStrategy = defaultIdStrategy): List 58 | 59 | fun unsafeBatchUpdate(values: List): List 60 | 61 | fun batchUpdate(values: List>): List 62 | 63 | fun allocateIds(count: Int): List 64 | } -------------------------------------------------------------------------------- /example/src/main/kotlin/com/github/andrewoma/kwery/example/film/resources/LanguageResource.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.example.film.resources 24 | 25 | import com.codahale.metrics.annotation.Timed 26 | import com.github.andrewoma.kwery.example.film.dao.LanguageDao 27 | import com.github.andrewoma.kwery.example.film.dao.languageTable 28 | import com.github.andrewoma.kwery.example.film.model.Language 29 | import com.github.andrewoma.kwery.fetcher.GraphFetcher 30 | import com.github.andrewoma.kwery.mapper.IdStrategy 31 | import com.github.andrewoma.kwery.transactional.jersey.Transactional 32 | import javax.ws.rs.* 33 | import javax.ws.rs.core.MediaType 34 | 35 | 36 | @Path("/languages") 37 | @Produces(MediaType.APPLICATION_JSON) 38 | @Transactional class LanguageResource(val languageDao: LanguageDao, override val fetcher: GraphFetcher) : Resource { 39 | @Timed @GET 40 | fun find(@QueryParam("name") name: String?): List { 41 | 42 | val filter = parameters(languageTable.Name optional name) 43 | 44 | return languageDao.findByExample(languageTable.copy(Language(), filter), filter.keys) 45 | } 46 | 47 | @Timed @GET @Path("/{id}") 48 | fun findById(@PathParam("id") id: Int): Language { 49 | return languageDao.findById(id) ?: throw NotFoundException("$id not found") 50 | } 51 | 52 | @Timed @POST 53 | fun create(language: Language): Int { 54 | return languageDao.insert(language.copy(version = 1), IdStrategy.Generated).id 55 | } 56 | 57 | @Timed @PUT @Path("/{id}") 58 | fun update(@PathParam("id") id: Int, language: Language): Int { 59 | return languageDao.update(Language(id).copy(version = language.version), language).version 60 | } 61 | 62 | @Timed @DELETE @Path("/{id}") 63 | fun delete(@PathParam("id") id: Int) { 64 | if (languageDao.delete(id) == 0) throw NotFoundException("$id not found") 65 | } 66 | } -------------------------------------------------------------------------------- /core/src/main/kotlin/com/github/andrewoma/kwery/core/BoundQuery.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core 24 | 25 | import java.util.regex.Pattern 26 | 27 | data class BoundQuery(val originalQuery: String, val query: String, val bindings: List) 28 | 29 | internal fun BoundQuery(query: String, inClauseSizes: Map): BoundQuery { 30 | val bindings = arrayListOf() 31 | val bound = replaceBindings(query) { key -> 32 | bindings.add(key) 33 | val size = inClauseSizes[key] ?: 1 34 | Array(size) { "?" }.joinToString(",") 35 | } 36 | return BoundQuery(query, bound, bindings) 37 | } 38 | 39 | internal inline fun replaceBindings(query: String, onBinding: (String) -> String): String { 40 | val pattern = Pattern.compile("""\:(([a-zA-Z_]+[0-9]*)+)""") 41 | val matcher = pattern.matcher(query) 42 | 43 | val result = StringBuffer() 44 | while (matcher.find()) { 45 | val key = matcher.group(1) 46 | matcher.appendReplacement(result, onBinding(key).replace("$", "\\$")) // Must escape group references 47 | } 48 | matcher.appendTail(result) 49 | return result.toString() 50 | } 51 | 52 | internal fun inClauseSizes(parametersList: List>): Map { 53 | val sizes: MutableMap = hashMapOf() 54 | 55 | for (parameters in parametersList) { 56 | for ((key, value) in parameters) { 57 | if (value is Collection<*> && value.size != 0) { 58 | sizes[key] = Math.max(inClauseSize(value.size), sizes[key] ?: 0) 59 | } 60 | } 61 | } 62 | 63 | return sizes 64 | } 65 | 66 | private fun inClauseSize(size: Int): Int { 67 | var num = size 68 | var count = 1 69 | while (num != 0) { 70 | num = num shr 1 71 | count++ 72 | } 73 | if (count < 3) count = 3 74 | return Math.pow(2.toDouble(), count.toDouble()).toInt() 75 | } 76 | -------------------------------------------------------------------------------- /mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/example/ActorDao.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mappertest.example 24 | 25 | import com.github.andrewoma.kwery.core.Session 26 | import com.github.andrewoma.kwery.mapper.AbstractDao 27 | import com.github.andrewoma.kwery.mapper.Table 28 | import com.github.andrewoma.kwery.mapper.Value 29 | import com.github.andrewoma.kwery.mapper.VersionedWithTimestamp 30 | import com.github.andrewoma.kwery.mappertest.example.Actor as A 31 | import com.github.andrewoma.kwery.mappertest.example.Name as N 32 | 33 | object actorTable : Table("actor", tableConfig, "actor_seq"), VersionedWithTimestamp { 34 | // @formatter:off 35 | val ActorId by col(A::id, id = true) 36 | val FirstName by col(N::firstName, A::name) 37 | val LastName by col(N::lastName, A::name) 38 | val LastUpdate by col(A::lastUpdate, version = true) 39 | // @formatter:on 40 | 41 | override fun idColumns(id: Int) = setOf(ActorId of id) 42 | 43 | override fun create(value: Value) = A(N(value.of(FirstName), value.of(LastName)), 44 | value.of(ActorId), value.of(LastUpdate)) 45 | } 46 | 47 | class ActorDao(session: Session, val filmActorDao: FilmActorDao) : 48 | AbstractDao(session, actorTable, A::id, "int", defaultId = -1) { 49 | 50 | fun findByLastNames(lastNames: List): List { 51 | val sql = "select $columns from ${table.name} where last_name in (:last_names)" 52 | val parameters = mapOf("last_names" to lastNames) 53 | return session.select(sql, parameters, options("findByLastNames"), table.rowMapper()) 54 | } 55 | 56 | fun findByFilmIds(ids: Collection): Map> { 57 | val filmActors = filmActorDao.findByFilmIds(ids) 58 | val films = findByIds(filmActors.map { it.id.actorId }.toSet()) 59 | return filmActors.groupBy { it.id.filmId }.mapValues { it.value.map { films[it.id.actorId]!! } } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /example/src/main/kotlin/com/github/andrewoma/kwery/example/film/dao/FilmDao.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.example.film.dao 24 | 25 | import com.github.andrewoma.kwery.core.Session 26 | import com.github.andrewoma.kwery.example.film.model.Film 27 | import com.github.andrewoma.kwery.example.film.model.FilmActor 28 | import com.github.andrewoma.kwery.mapper.* 29 | import java.time.temporal.ChronoUnit 30 | import com.github.andrewoma.kwery.example.film.model.Film as F 31 | 32 | object filmTable : Table("film", tableConfig), VersionedWithInt { 33 | // @formatter:off 34 | val Id by col(F::id, id = true) 35 | val Title by col(F::title) 36 | val Description by col(F::description) 37 | val ReleaseYear by col(F::releaseYear) 38 | val LanguageId by col(F::language) 39 | val OriginalLanguageId by col(F::originalLanguage) 40 | val Length by col(F::duration, converter = optional(DurationConverter(ChronoUnit.SECONDS))) 41 | val Rating by col(F::rating) 42 | val Version by col(F::version, version = true) 43 | val SpecialFeatures by col(F::specialFeatures, default = listOf(), converter = ArrayConverter("varchar")) 44 | // @formatter:on 45 | 46 | override fun idColumns(id: Int) = setOf(Id of id) 47 | 48 | override fun create(value: Value): F = F(value of Id, value of Title, value of Description, 49 | value of ReleaseYear, value of LanguageId, value of OriginalLanguageId, 50 | value of Length, value of Rating, value of SpecialFeatures, version = value of Version) 51 | } 52 | 53 | class FilmDao(session: Session) : AbstractDao(session, filmTable, { it.id }, "int", defaultId = 0) { 54 | 55 | fun findByActorIds(filmActors: Collection): Map> { 56 | val films = findByIds(filmActors.map { it.id.filmId }.toSet()) 57 | return filmActors.groupBy { it.id.filmId }.mapValues { it.value.map { films[it.id.filmId]!! } } 58 | } 59 | } -------------------------------------------------------------------------------- /transactional/src/main/kotlin/com/github/andrewoma/kwery/transactional/transactionalFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.transactional 24 | 25 | import net.sf.cglib.proxy.Enhancer 26 | import net.sf.cglib.proxy.MethodInterceptor 27 | import org.aopalliance.intercept.MethodInvocation 28 | import kotlin.reflect.KProperty1 29 | import kotlin.reflect.jvm.javaField 30 | 31 | object transactionalFactory { 32 | fun fromInterfaces(obj: T, interfaces: Array> = obj::class.java.interfaces): T { 33 | val enhancer = Enhancer() 34 | enhancer.setInterfaces(interfaces) 35 | enhancer.setCallback(MethodInterceptor { _, method, args, proxy -> 36 | TransactionalInterceptor().invoke(object : MethodInvocation { 37 | override fun getThis() = obj 38 | override fun getStaticPart() = method 39 | override fun proceed() = proxy.invoke(obj, args) 40 | override fun getMethod() = method 41 | override fun getArguments() = args 42 | }) 43 | }) 44 | 45 | @Suppress("UNCHECKED_CAST") 46 | return enhancer.create() as T 47 | } 48 | 49 | fun fromClass(obj: T, vararg args: KProperty1): T { 50 | return fromClass(obj, args.map { it.javaField!!.type }.toTypedArray(), args.map { it.get(obj) }.toTypedArray()) 51 | } 52 | 53 | fun fromClass(obj: T, argTypes: Array>, args: Array): T { 54 | val enhancer = Enhancer() 55 | enhancer.setSuperclass(obj::class.java) 56 | enhancer.setCallback(MethodInterceptor { _, method, methodArgs, proxy -> 57 | TransactionalInterceptor().invoke(object : MethodInvocation { 58 | override fun getThis() = obj 59 | override fun getStaticPart() = method 60 | override fun proceed() = proxy.invoke(obj, methodArgs) 61 | override fun getMethod() = method 62 | override fun getArguments() = methodArgs 63 | }) 64 | }) 65 | 66 | @Suppress("UNCHECKED_CAST") 67 | return enhancer.create(argTypes, args) as T 68 | } 69 | } -------------------------------------------------------------------------------- /transactional/src/main/kotlin/com/github/andrewoma/kwery/transactional/TransactionalInterceptor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.transactional 24 | 25 | import com.github.andrewoma.kwery.core.ManagedThreadLocalSession 26 | import org.aopalliance.intercept.MethodInterceptor 27 | import org.aopalliance.intercept.MethodInvocation 28 | import kotlin.reflect.KClass 29 | 30 | class TransactionalInterceptor : MethodInterceptor { 31 | 32 | override fun invoke(invocation: MethodInvocation): Any? { 33 | val transactional = getTransactional(invocation) 34 | 35 | @Suppress("IMPLICIT_CAST_TO_UNIT_OR_ANY") 36 | return if (transactional == null || ManagedThreadLocalSession.isInitialised(transactional.name)) { 37 | invocation.proceed() 38 | } else { 39 | invoke(transactional, invocation) 40 | } 41 | } 42 | 43 | private fun invoke(transactional: Transactional, invocation: MethodInvocation): Any? { 44 | var commit = true 45 | try { 46 | ManagedThreadLocalSession.initialise(!transactional.manual, transactional.name) 47 | return invocation.proceed() 48 | } catch(e: Exception) { 49 | commit = !rollbackOnException(transactional, e) 50 | throw e 51 | } finally { 52 | ManagedThreadLocalSession.finalise(commit, transactional.name) 53 | } 54 | } 55 | 56 | // Hacks to work around annotations KClass to Class conversions not working in M12 57 | // TODO - Remove this when conversions via filter functions don't throw ClassCastException 58 | private fun isInstance(exceptions: Array>, exception: Exception): Boolean { 59 | return exceptions 60 | .map { it.java } 61 | .any { it.isInstance(exception) } 62 | } 63 | 64 | private fun rollbackOnException(transactional: Transactional, e: Exception): Boolean { 65 | return isInstance(transactional.rollbackOn, e) && !isInstance(transactional.ignore, e) 66 | } 67 | 68 | private fun getTransactional(invocation: MethodInvocation) = 69 | invocation.method.getAnnotation(Transactional::class.java) 70 | ?: invocation.`this`::class.java.getAnnotation(Transactional::class.java) 71 | } 72 | 73 | -------------------------------------------------------------------------------- /core/src/test/kotlin/com/github/andrewoma/kwery/core/AbstractSessionTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core 24 | 25 | import com.github.andrewoma.kwery.core.dialect.Dialect 26 | import com.github.andrewoma.kwery.core.dialect.HsqlDialect 27 | import com.github.andrewoma.kwery.core.interceptor.LoggingInterceptor 28 | import org.junit.After 29 | import org.junit.Before 30 | import org.junit.Rule 31 | import org.junit.rules.TestName 32 | import javax.sql.DataSource 33 | import kotlin.properties.Delegates 34 | 35 | abstract class AbstractSessionTest(val dataSource: DataSource = hsqlDataSource, val dialect: Dialect = HsqlDialect()) { 36 | companion object { 37 | val initialised: MutableSet = hashSetOf() 38 | } 39 | 40 | var transaction: ManualTransaction by Delegates.notNull() 41 | var session: Session by Delegates.notNull() 42 | 43 | open var startTransactionByDefault: Boolean = true 44 | open var rollbackTransactionByDefault: Boolean = false 45 | 46 | val name = TestName() 47 | @Rule fun name(): TestName = name // Annotating val directly doesn't work 48 | 49 | @Before fun setUp() { 50 | session = DefaultSession(dataSource.connection, dialect, LoggingInterceptor()) 51 | if (startTransactionByDefault) { 52 | transaction = session.manualTransaction() 53 | } 54 | 55 | afterSessionSetup() 56 | println("\n==== Starting '${name.methodName}'") 57 | } 58 | 59 | open fun afterSessionSetup() { 60 | } 61 | 62 | fun initialise(token: String, f: () -> Unit) { 63 | if (initialised.contains(token)) return 64 | initialised.add(token) 65 | f() 66 | } 67 | 68 | @After fun tearDown() { 69 | try { 70 | if (startTransactionByDefault) { 71 | if (rollbackTransactionByDefault || transaction.rollbackOnly) { 72 | transaction.rollback() 73 | } else { 74 | transaction.commit() 75 | } 76 | } 77 | } finally { 78 | session.connection.close() 79 | } 80 | } 81 | } 82 | 83 | val dbNameSql = """ 84 | select character_value as name from information_schema.sql_implementation_info 85 | where implementation_info_name = 'DBMS NAME' 86 | """ 87 | -------------------------------------------------------------------------------- /mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/example/test/ActorDaoTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mappertest.example.test 24 | 25 | import com.github.andrewoma.kwery.mappertest.example.* 26 | import org.junit.Test 27 | import kotlin.properties.Delegates 28 | import kotlin.test.assertEquals 29 | import kotlin.test.assertFalse 30 | import kotlin.test.assertTrue 31 | 32 | class ActorDaoTest : AbstractFilmDaoTest() { 33 | override var dao: ActorDao by Delegates.notNull() 34 | 35 | override fun afterSessionSetup() { 36 | dao = ActorDao(session, FilmActorDao(session)) 37 | super.afterSessionSetup() 38 | } 39 | 40 | override val data = listOf( 41 | Actor(Name("John", "Wayne")), 42 | Actor(Name("Meg", "Ryan")), 43 | Actor(Name("Jeff", "Bridges"), ++staticId), 44 | Actor(Name("Yvonne", "Strahovsky"), ++staticId) 45 | ) 46 | 47 | override fun mutateContents(t: Actor) = t.copy(name = Name("Bradley", "Cooper")) 48 | 49 | override fun contentsEqual(t1: Actor, t2: Actor) = 50 | t1.name == t2.name 51 | 52 | 53 | @Test fun `findByExample matches example given`() { 54 | insertAll() 55 | val result = dao.findByExample(Actor().copy(name = Name("", lastName = "Ryan")), setOf(actorTable.LastName)) 56 | assertEquals(1, result.size) 57 | assertEquals("Meg", result.first().name.firstName) 58 | } 59 | 60 | @Test fun `findByLastNames matches multiple names`() { 61 | insertAll() 62 | val names = dao.findByLastNames(listOf("Ryan", "Bridges")).map { it.name } 63 | assertTrue(names.containsAll(setOf(Name("Jeff", "Bridges"), Name("Meg", "Ryan")))) 64 | assertFalse(names.contains(Name("Yvonne", "Strahovsky"))) 65 | } 66 | 67 | @Test fun `Insert with default key should result in a generated key`() { 68 | val actor = data[0] 69 | val inserted = dao.insert(actor) 70 | assertTrue(contentsEqual(actor, inserted)) 71 | assertFalse(actor.id == inserted.id) 72 | } 73 | 74 | @Test fun `Insert with non-default key should insert given key`() { 75 | val actor = data[3] 76 | val inserted = dao.insert(actor) 77 | assertTrue(contentsEqual(actor, inserted)) 78 | assertTrue(actor.id == inserted.id) 79 | } 80 | } -------------------------------------------------------------------------------- /mapper/src/test/kotlin/com/github/andrewoma/kwery/mappertest/AbstractSessionTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mappertest 24 | 25 | import com.github.andrewoma.kwery.core.DefaultSession 26 | import com.github.andrewoma.kwery.core.ManualTransaction 27 | import com.github.andrewoma.kwery.core.Session 28 | import com.github.andrewoma.kwery.core.dialect.Dialect 29 | import com.github.andrewoma.kwery.core.dialect.HsqlDialect 30 | import com.github.andrewoma.kwery.core.interceptor.LoggingInterceptor 31 | import com.zaxxer.hikari.HikariDataSource 32 | import org.junit.After 33 | import org.junit.Before 34 | import org.junit.Rule 35 | import org.junit.rules.TestName 36 | import kotlin.properties.Delegates 37 | 38 | val testDataSource = HikariDataSource().apply { 39 | driverClassName = "org.hsqldb.jdbc.JDBCDriver" 40 | jdbcUrl = "jdbc:hsqldb:mem:kwerydao" 41 | } 42 | 43 | abstract class AbstractSessionTest(val dataSource: javax.sql.DataSource = testDataSource, val dialect: Dialect = HsqlDialect()) { 44 | companion object { 45 | val initialised = hashMapOf() 46 | } 47 | 48 | var transaction: ManualTransaction by Delegates.notNull() 49 | var session: Session by Delegates.notNull() 50 | 51 | open var startTransactionByDefault: Boolean = true 52 | open var rollbackTransactionByDefault: Boolean = false 53 | 54 | val name = TestName() 55 | @Rule fun name(): TestName = name // Annotating val directly doesn't work 56 | 57 | @Before fun setUp() { 58 | session = DefaultSession(dataSource.connection, dialect, LoggingInterceptor()) 59 | if (startTransactionByDefault) { 60 | transaction = session.manualTransaction() 61 | } 62 | 63 | afterSessionSetup() 64 | println("==== Starting '${name.methodName}'") 65 | } 66 | 67 | open fun afterSessionSetup() { 68 | } 69 | 70 | @Suppress("UNCHECKED_CAST", "DEPRECATION") 71 | fun initialise(token: String, f: (Session) -> R): R { 72 | return initialised.getOrPut(token) { f(session) } as R 73 | } 74 | 75 | @After fun tearDown() { 76 | try { 77 | if (startTransactionByDefault) { 78 | if (rollbackTransactionByDefault || transaction.rollbackOnly) { 79 | transaction.rollback() 80 | } else { 81 | transaction.commit() 82 | } 83 | } 84 | } finally { 85 | session.connection.close() 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /fetcher/src/test/kotlin/com/github/andrewoma/kwery/fetcher/readme/Readme.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.fetcher.readme 24 | 25 | import com.github.andrewoma.kwery.fetcher.* 26 | 27 | // Given the following domain model 28 | data class Actor(val id: Int, val firstName: String, val lastName: String) 29 | 30 | data class Language(val id: Int, val name: String) 31 | 32 | data class Film(val id: Int, val language: Language, val actors: Set, 33 | val title: String, val releaseYear: Int) 34 | 35 | @Suppress("UNUSED_PARAMETER") 36 | class Dao { 37 | fun findByIds(id: Collection): Map = mapOf() 38 | fun findByFilmIds(id: Collection): Map> = mapOf() 39 | fun findFilmsReleasedAfter(year: Int): List = listOf() 40 | } 41 | 42 | fun readme() { 43 | val languageDao = Dao() 44 | val filmDao = Dao() 45 | val actorDao = Dao() 46 | 47 | // Define types with functions describing how to fetch a batch by ids 48 | val language = Type(Language::id, { languageDao.findByIds(it) }) 49 | val actor = Type(Actor::id, { actorDao.findByIds(it) }) 50 | 51 | // For types that reference other types describe how to apply fetched values 52 | val film = Type(Film::id, { filmDao.findByIds(it) }, listOf( 53 | // 1 to 1 54 | Property(Film::language, language, { it.language.id }, { f, l -> f.copy(language = l) }), 55 | 56 | // 1 to many requires a function to describe how to fetch the related objects 57 | CollectionProperty(Film::actors, actor, { it.id }, 58 | { f, a -> f.copy(actors = a.toSet()) }, 59 | { actorDao.findByFilmIds(it) }) 60 | )) 61 | 62 | val fetcher = GraphFetcher(setOf(language, actor, film)) 63 | 64 | // Extension function to fetch the graph for any List using fetcher defined above 65 | fun List.fetch(vararg nodes: Node) = fetcher.fetch(this, Node.create("", nodes.toSet())) 66 | 67 | // We can now efficiently fetch various graphs for any list of films 68 | // The following fetches the films with actors and languages in 3 queries 69 | val filmsWithAll = filmDao.findFilmsReleasedAfter(2010).fetch(Node.all) 70 | 71 | // The graph specification can also be built using properties 72 | val filmsWithActors = filmDao.findFilmsReleasedAfter(2010).fetch(Film::actors.node()) 73 | 74 | println("$filmsWithAll $filmsWithActors") 75 | } 76 | 77 | fun main(args: Array) { 78 | readme() 79 | } -------------------------------------------------------------------------------- /fetcher/src/main/kotlin/com/github/andrewoma/kwery/fetcher/Type.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.fetcher 24 | 25 | import kotlin.reflect.KProperty1 26 | 27 | inline fun Type(noinline id: (T) -> ID, noinline fetch: (Collection) -> Map, properties: List> = listOf()): Type { 28 | return Type(T::class.java, id, fetch, properties) 29 | } 30 | 31 | inline fun Type(id: KProperty1, noinline fetch: (Collection) -> Map, properties: List> = listOf()): Type { 32 | return Type(T::class.java, { id.get(it) }, fetch, properties) 33 | } 34 | 35 | open class Type( 36 | val javaClass: Class, 37 | val id: (T) -> ID, 38 | val fetch: (Collection) -> Map, 39 | var properties: List> = listOf() // Mutable to support cycles 40 | ) { 41 | open fun supports(obj: Any?) = obj?.let { it::class.java }?.isAssignableFrom(javaClass)!! 42 | 43 | override fun toString() = javaClass.simpleName + "(" + properties.map { it.name }.joinToString(", ") + ")" 44 | } 45 | 46 | @Suppress("BASE_WITH_NULLABLE_UPPER_BOUND") 47 | open class BaseProperty( 48 | val id: (C) -> ID?, 49 | val type: Type, 50 | val name: String 51 | ) 52 | 53 | @Suppress("BASE_WITH_NULLABLE_UPPER_BOUND") 54 | class Property( 55 | val get: (C) -> T?, 56 | type: Type, 57 | id: (C) -> ID?, 58 | val apply: (C, T) -> C, 59 | name: String 60 | ) : BaseProperty(id, type, name) 61 | 62 | @Suppress("BASE_WITH_NULLABLE_UPPER_BOUND") 63 | fun Property(property: KProperty1, 64 | type: Type, 65 | id: (C) -> ID?, 66 | apply: (C, T) -> C 67 | ): Property = Property({ property.get(it) }, type, id, apply, property.name) 68 | 69 | class CollectionProperty( 70 | type: Type, 71 | id: (C) -> ID, 72 | val apply: (C, Collection) -> C, 73 | val fetch: (Collection) -> Map>, 74 | name: String 75 | ) : BaseProperty(id, type, name) { 76 | override fun toString() = name 77 | } 78 | 79 | fun CollectionProperty( 80 | property: KProperty1>, 81 | type: Type, 82 | id: (C) -> ID, 83 | apply: (C, Collection) -> C, 84 | fetch: (Collection) -> Map> 85 | ): CollectionProperty = CollectionProperty(type, id, apply, fetch, property.name) 86 | -------------------------------------------------------------------------------- /example/src/main/kotlin/com/github/andrewoma/kwery/example/film/jackson/JacksonExtensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.example.film.jackson 24 | 25 | import com.fasterxml.jackson.annotation.JsonFilter 26 | import com.fasterxml.jackson.core.JsonGenerator 27 | import com.fasterxml.jackson.core.JsonToken 28 | import com.fasterxml.jackson.databind.ObjectMapper 29 | import com.fasterxml.jackson.databind.SerializerProvider 30 | import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor 31 | import com.fasterxml.jackson.databind.node.ObjectNode 32 | import com.fasterxml.jackson.databind.ser.PropertyFilter 33 | import com.fasterxml.jackson.databind.ser.PropertyWriter 34 | import com.github.andrewoma.kwery.example.film.model.AttributeSet 35 | import com.github.andrewoma.kwery.example.film.model.HasAttributeSet 36 | import java.net.URL 37 | 38 | inline fun ObjectMapper.withObjectStream(url: URL, f: (Sequence) -> Unit) { 39 | val parser = this.factory.createParser(url) 40 | check(parser.nextToken() == JsonToken.START_ARRAY) { "Expected an array" } 41 | check(parser.nextToken() == JsonToken.START_OBJECT) { "Expected an object" } 42 | 43 | try { 44 | val iterator = this.readValues(parser, T::class.java) 45 | f(object : Sequence { 46 | override fun iterator() = iterator 47 | }) 48 | } finally { 49 | parser.close() 50 | } 51 | } 52 | 53 | // TODO ... sort out how to handle deserialisation of unspecified values into non-null fields 54 | 55 | class AttributeSetFilter : PropertyFilter { 56 | override fun serializeAsField(pojo: Any, generator: JsonGenerator, provider: SerializerProvider, writer: PropertyWriter) { 57 | val partial = pojo as HasAttributeSet 58 | if (partial.attributeSet() == AttributeSet.All || writer.name == "id") { 59 | writer.serializeAsField(pojo, generator, provider) 60 | } else { 61 | writer.serializeAsOmittedField(pojo, generator, provider) 62 | } 63 | } 64 | 65 | override fun serializeAsElement(p0: Any?, p1: JsonGenerator?, p2: SerializerProvider?, p3: PropertyWriter?) { 66 | throw UnsupportedOperationException() 67 | } 68 | 69 | override fun depositSchemaProperty(p0: PropertyWriter?, p1: ObjectNode?, p2: SerializerProvider?) { 70 | throw UnsupportedOperationException() 71 | } 72 | 73 | override fun depositSchemaProperty(p0: PropertyWriter?, p1: JsonObjectFormatVisitor?, p2: SerializerProvider?) { 74 | throw UnsupportedOperationException() 75 | } 76 | } 77 | 78 | @JsonFilter("Attribute set filter") 79 | class AttributeSetFilterMixIn 80 | -------------------------------------------------------------------------------- /mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/KeyRow.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mapper 24 | 25 | import com.github.andrewoma.kwery.core.Row 26 | import java.sql.ResultSet 27 | 28 | /** 29 | * A variant of row that fetches the generated key from the result set 30 | * by column index, not name 31 | */ 32 | class KeyRow(resultSet: ResultSet) : Row(resultSet) { 33 | override fun int(name: String) = resultSet.getInt(1) 34 | override fun intOrNull(name: String) = resultSet.getInt(1) 35 | override fun long(name: String) = resultSet.getLong(1) 36 | override fun longOrNull(name: String) = resultSet.getLong(1) 37 | 38 | override fun obj(name: String) = unsupported() 39 | override fun objectOrNull(name: String) = unsupported() 40 | override fun boolean(name: String) = unsupported() 41 | override fun booleanOrNull(name: String) = unsupported() 42 | override fun byte(name: String) = unsupported() 43 | override fun byteOrNull(name: String) = unsupported() 44 | override fun short(name: String) = unsupported() 45 | override fun shortOrNull(name: String) = unsupported() 46 | override fun float(name: String) = unsupported() 47 | override fun floatOrNull(name: String) = unsupported() 48 | override fun double(name: String) = unsupported() 49 | override fun doubleOrNull(name: String) = unsupported() 50 | override fun bigDecimal(name: String) = unsupported() 51 | override fun bigDecimalOrNull(name: String) = unsupported() 52 | override fun string(name: String) = unsupported() 53 | override fun stringOrNull(name: String) = unsupported() 54 | override fun bytes(name: String) = unsupported() 55 | override fun bytesOrNull(name: String) = unsupported() 56 | override fun timestamp(name: String) = unsupported() 57 | override fun timestampOrNull(name: String) = unsupported() 58 | override fun time(name: String) = unsupported() 59 | override fun timeOrNull(name: String) = unsupported() 60 | override fun date(name: String) = unsupported() 61 | override fun dateOrNull(name: String) = unsupported() 62 | override fun clob(name: String) = unsupported() 63 | override fun clobOrNull(name: String) = unsupported() 64 | override fun blob(name: String) = unsupported() 65 | override fun blobOrNull(name: String) = unsupported() 66 | override fun characterStream(name: String) = unsupported() 67 | override fun characterStreamOrNull(name: String) = unsupported() 68 | override fun binaryStream(name: String) = unsupported() 69 | override fun binaryStreamOrNull(name: String) = unsupported() 70 | 71 | private fun unsupported(): Nothing { 72 | throw UnsupportedOperationException("Only Long and Int generated keys are supported") 73 | } 74 | } -------------------------------------------------------------------------------- /mapper/src/main/kotlin/com/github/andrewoma/kwery/mapper/listener/DaoListener.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.mapper.listener 24 | 25 | import com.github.andrewoma.kwery.core.Session 26 | import com.github.andrewoma.kwery.core.Transaction 27 | import com.github.andrewoma.kwery.mapper.Table 28 | import java.util.concurrent.ConcurrentHashMap 29 | 30 | interface Listener { 31 | fun onEvent(session: Session, event: Event) 32 | } 33 | 34 | interface Event { 35 | val table: Table<*, *> 36 | val id: Any 37 | } 38 | 39 | interface TransformingEvent : Event { 40 | val new: Any 41 | var transformed: Any 42 | } 43 | 44 | data class PreInsertEvent( 45 | override val table: Table<*, *>, 46 | override val id: Any, 47 | override val new: Any, 48 | override var transformed: Any = new 49 | ) : TransformingEvent 50 | 51 | data class PreUpdateEvent( 52 | override val table: Table<*, *>, 53 | override val id: Any, 54 | override val new: Any, 55 | val old: Any?, 56 | override var transformed: Any = new 57 | ) : TransformingEvent 58 | 59 | data class InsertEvent(override val table: Table<*, *>, override val id: Any, val value: Any) : Event 60 | data class DeleteEvent(override val table: Table<*, *>, override val id: Any, val value: Any?) : Event 61 | data class UpdateEvent(override val table: Table<*, *>, override val id: Any, val new: Any, val old: Any?) : Event 62 | 63 | abstract class DeferredListener(val postCommit: Boolean = true) : Listener { 64 | private val eventsByTransaction = ConcurrentHashMap>() 65 | 66 | override fun onEvent(session: Session, event: Event) { 67 | val transaction = session.currentTransaction 68 | if (transaction == null) { 69 | // If there is no transaction, assume auto commit is true 70 | onCommit(true, listOf(event)) 71 | } else { 72 | if (!eventsByTransaction.containsKey(transaction.id)) { 73 | addCommitHook(transaction) 74 | } 75 | 76 | eventsByTransaction[transaction.id]?.add(event) 77 | } 78 | } 79 | 80 | private fun addCommitHook(transaction: Transaction) { 81 | eventsByTransaction[transaction.id] = arrayListOf() 82 | 83 | val onComplete: (Boolean, Session) -> Unit = { committed, _ -> 84 | val events = eventsByTransaction[transaction.id]!! 85 | eventsByTransaction.remove(transaction.id) 86 | onCommit(committed, events) 87 | } 88 | 89 | if (postCommit) { 90 | transaction.postCommitHandler(onComplete) 91 | } else { 92 | transaction.preCommitHandler { session -> onComplete(true, session) } 93 | } 94 | } 95 | 96 | abstract fun onCommit(committed: Boolean, events: List) 97 | } 98 | -------------------------------------------------------------------------------- /example/src/main/kotlin/com/github/andrewoma/kwery/example/film/jersey/LoggingListener.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.example.film.jersey 24 | 25 | import com.github.andrewoma.kwery.core.interceptor.LoggingInterceptor 26 | import com.github.andrewoma.kwery.core.interceptor.LoggingSummaryInterceptor 27 | import org.glassfish.jersey.server.monitoring.ApplicationEvent 28 | import org.glassfish.jersey.server.monitoring.ApplicationEventListener 29 | import org.glassfish.jersey.server.monitoring.RequestEvent 30 | import org.glassfish.jersey.server.monitoring.RequestEventListener 31 | import org.slf4j.LoggerFactory 32 | import javax.ws.rs.ext.Provider 33 | 34 | 35 | @Provider 36 | class LoggingListener : ApplicationEventListener { 37 | private val log = LoggerFactory.getLogger(LoggingListener::class.java) 38 | 39 | enum class LogType { none, summary, statements, all } 40 | 41 | override fun onEvent(event: ApplicationEvent) { 42 | } 43 | 44 | override fun onRequest(requestEvent: RequestEvent): RequestEventListener { 45 | return Listener() 46 | } 47 | 48 | private inner class Listener : RequestEventListener { 49 | var logType = LogType.summary 50 | 51 | override fun onEvent(event: RequestEvent) { 52 | try { 53 | val type = event.type 54 | 55 | if (type == RequestEvent.Type.MATCHING_START) { 56 | logType = getLogType(event) 57 | if (logType == LogType.summary || logType == LogType.all) { 58 | LoggingSummaryInterceptor.start() 59 | } 60 | if (logType == LogType.statements || logType == LogType.all) { 61 | LoggingInterceptor.forceLogging.set(true) 62 | } 63 | } else if (type == RequestEvent.Type.FINISHED) { 64 | if (logType == LogType.summary || logType == LogType.all) { 65 | LoggingSummaryInterceptor.stop() 66 | } 67 | if (logType == LogType.statements || logType == LogType.all) { 68 | LoggingInterceptor.forceLogging.remove() 69 | } 70 | } 71 | } catch(e: Exception) { 72 | log.error("Error processing log listener", e) 73 | } 74 | } 75 | 76 | private fun getLogType(event: RequestEvent): LogType { 77 | val logParam = event.containerRequest.uriInfo.queryParameters.getFirst("log") 78 | return if (logParam == null) LogType.summary else { 79 | try { 80 | LogType.valueOf(logParam) 81 | } catch(e: Exception) { 82 | log.warn("Invalid logging value: $logParam") 83 | LogType.summary 84 | } 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /core/src/test/kotlin/com/github/andrewoma/kwery/core/AbstractFilmSessionTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.core 24 | 25 | import org.junit.Assert.assertEquals 26 | import java.sql.Timestamp 27 | 28 | data class Actor(val firstName: String, val lastName: String?, val id: Int = 0, val lastUpdate: Timestamp = Timestamp(System.currentTimeMillis())) 29 | 30 | open class AbstractFilmSessionTest : AbstractSessionTest() { 31 | 32 | val actorMapper: (Row) -> Actor = { row -> 33 | Actor(row.string("first_name"), row.stringOrNull("last_name"), row.int("actor_id"), row.timestamp("last_update")) 34 | } 35 | 36 | override fun afterSessionSetup() = initialise(AbstractFilmSessionTest::class.java.name) { 37 | //language=SQL 38 | val sql = """ 39 | create table actor ( 40 | actor_id integer identity, 41 | first_name character varying(45) not null, 42 | last_name character varying(45) null, 43 | last_update timestamp not null 44 | ) 45 | """ 46 | 47 | for (statement in sql.split(";".toRegex())) { 48 | session.update(statement) 49 | } 50 | 51 | session.update("delete from actor") 52 | } 53 | 54 | fun Actor.toMap(): Map = mapOf( 55 | "first_name" to this.firstName, 56 | "last_name" to this.lastName, 57 | "last_update" to this.lastUpdate, 58 | "actor_id" to this.id 59 | ) 60 | 61 | fun insert(actor: Actor): Actor { 62 | return if (actor.id == 0) { 63 | val sql = "insert into actor(first_name, last_name, last_update) values (:first_name, :last_name, :last_update)" 64 | val (count, key) = session.insert(sql, actor.toMap(), StatementOptions(useGeneratedKeys = true)) { it.resultSet.getInt(1) } 65 | assertEquals(1, count) 66 | actor.copy(id = key) 67 | } else { 68 | val sql = "insert into actor(actor_id, first_name, last_name, last_update) values (:actor_id, :first_name, :last_name, :last_update)" 69 | val count = session.update(sql, actor.toMap()) 70 | assertEquals(1, count) 71 | actor 72 | } 73 | } 74 | 75 | fun deleteActor(id: Int) = session.update("delete from actor where actor_id = :id", mapOf("id" to id)) 76 | 77 | fun maxId(id: String, table: String): Int { 78 | return session.select("select max($id) id from $table") { row -> row.intOrNull("id") }.first() ?: 0 79 | } 80 | 81 | fun selectActors(ids: Set = setOf()): List { 82 | val params = hashMapOf() 83 | val sql = "select actor_id, first_name, last_name, last_update from actor " + if (ids.isEmpty()) "" else { 84 | params["ids"] = ids 85 | "where actor_id in (:ids) order by actor_id" 86 | } 87 | return session.select(sql, params, mapper = actorMapper) 88 | } 89 | } -------------------------------------------------------------------------------- /example/src/test/kotlin/com/github/andrewoma/kwery/example/film/FilmApplicationAcceptanceTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015 Andrew O'Malley 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package com.github.andrewoma.kwery.example.film 24 | 25 | import io.dropwizard.testing.ResourceHelpers 26 | import io.dropwizard.testing.junit.DropwizardAppRule 27 | import org.junit.ClassRule 28 | import org.junit.Test 29 | import javax.ws.rs.client.ClientBuilder 30 | import kotlin.test.assertEquals 31 | 32 | class FilmApplicationAcceptanceTest { 33 | companion object { 34 | @ClassRule @JvmField 35 | val rule: DropwizardAppRule = 36 | DropwizardAppRule(FilmApplication::class.java, ResourceHelpers.resourceFilePath("dev.yml")) 37 | } 38 | 39 | fun target(url: String) = ClientBuilder.newClient().target("http://localhost:${rule.localPort}$url") 40 | 41 | @Test fun `Actors should find Scarlett`() { 42 | val response = target("/api/actors") 43 | .queryParam("firstName", "Scarlett") 44 | .queryParam("lastName", "Damon") 45 | .request().get(String::class.java) 46 | 47 | val expected = """ 48 | [ { 49 | "id" : 81, 50 | "name" : { 51 | "first" : "Scarlett", 52 | "last" : "Damon" 53 | }, 54 | "version" : 1, 55 | "films" : [ ] 56 | } ] 57 | """ 58 | assertEquals(expected.trimIndent(), response) 59 | } 60 | 61 | @Test fun `Languages should find English`() { 62 | 63 | val response = target("/api/languages") 64 | .queryParam("name", "English") 65 | .request().get(String::class.java) 66 | 67 | val expected = """ 68 | [ { 69 | "id" : 1, 70 | "name" : "English", 71 | "version" : 1 72 | } ] 73 | """ 74 | assertEquals(expected.trimIndent(), response) 75 | } 76 | 77 | @Test fun `Films should find Ace Goldfinger`() { 78 | 79 | val response = target("/api/films") 80 | .queryParam("title", "Ace Goldfinger") 81 | .request().get(String::class.java) 82 | 83 | val expected = """ 84 | [ { 85 | "id" : 2, 86 | "title" : "Ace Goldfinger", 87 | "description" : "A Astounding Epistle of a Database Administrator And a Explorer who must Find a Car in Ancient China", 88 | "releaseYear" : 2006, 89 | "language" : { 90 | "id" : 1 91 | }, 92 | "originalLanguage" : null, 93 | "duration" : 2880.000000000, 94 | "rating" : "G", 95 | "specialFeatures" : [ "Trailers", "Deleted Scenes" ], 96 | "actors" : [ ], 97 | "version" : 1 98 | } ] 99 | """ 100 | assertEquals(expected.trimIndent(), response) 101 | } 102 | } --------------------------------------------------------------------------------