forked from medly/norm
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'upstream/master'
- Loading branch information
Showing
3 changed files
with
184 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,4 @@ | |
# Ignore Gradle build output directory | ||
build | ||
|
||
**/out/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,182 @@ | ||
## Norm (Proof of Concept) | ||
## Norm | ||
|
||
## Table of Contents | ||
- [Concept](#concept) | ||
- [Design](#design) | ||
- [codegen](#codegen) | ||
- [runtime](#runtime) | ||
- [Getting Started](#getting-started) | ||
|
||
### Concept: | ||
|
||
**Norm(No-ORM)**, as the name suggests, is a library whose idealogy is opposite to that of how standard ORMs work. Norm is designed to avoid two things: | ||
1. Having to write entity, query and result/response classes by hand | ||
2. Writing queries/commands which would throw syntax or semantic errors at runtime | ||
|
||
|
||
### Design: | ||
|
||
It is a library for Kotlin and postgres which enables generating query/command classes at compile time if the query is syntactically and semantically right, and provides methods to execute them on runtime. | ||
|
||
Hence, Norm has two packages: | ||
|
||
1. ### **codegen** | ||
Given a postgres sql query or command in the form of a .sql file, this package generates a query class, params class, paramSetter class and resultMapper class in Kotlin. | ||
|
||
#### How it works: | ||
|
||
a. It needs a postgres connection | ||
|
||
b. Analyzes the sql file by creating a [PreparedStatement](https://docs.oracle.com/javase/7/docs/api/java/sql/PreparedStatement.html) | ||
|
||
c. If no database errors, creates a [SqlModel](https://github.com/medly/norm/blob/master/codegen/src/main/kotlin/norm/SqlAnalyzer.kt) which acts as an input to CodeGenerator | ||
|
||
d. Kotlin file with classes of Query or Command, ParamSetter and RowMapper are generated from SqlModel | ||
|
||
|
||
2. ### **runtime** | ||
|
||
#### What it does: | ||
|
||
a. Provides [interfaces](https://github.com/medly/norm/blob/master/runtime/src/main/kotlin/norm/TypedSqlExtensions.kt) which are super types for all query, command, params, row mapper classes. | ||
|
||
b. Provides multiple [functions](https://github.com/medly/norm/blob/master/runtime/src/main/kotlin/norm/SqlExtensions.kt) on these interfaces to be able to execute query/command, map result to a list, execute batch commands and queries etc. | ||
|
||
These methods run against a [Connection](https://docs.oracle.com/javase/7/docs/api/java/sql/Connection.html) or on a [ResultSet](https://docs.oracle.com/javase/7/docs/api/java/sql/ResultSet.html) or on a [PreparedStatement](https://docs.oracle.com/javase/7/docs/api/java/sql/PreparedStatement.html). | ||
|
||
### Getting Started: | ||
1. Setup | ||
- Start postgres server locally | ||
- Add norm dependencies | ||
```` | ||
configurations { | ||
norm | ||
} | ||
dependencies { | ||
implementation "org.postgresql:postgresql:$postgresVersion" | ||
norm 'com.github.medly.norm:codegen:$normVersion' | ||
norm 'com.github.medly.norm:runtime:$normVersion' | ||
} | ||
*Pre-requisites*: kotlin dependencies | ||
2. Schema and table creation | ||
- Create schemas and tables needed for your application or repository. | ||
- Lets take example of a ```person table``` and create it in the default ```public schema``` of default ```postgres database``` | ||
``` | ||
psql -p 5432 -d postgres | ||
postgres=# create table persons( | ||
id SERIAL PRIMARY KEY, | ||
name VARCHAR, | ||
age INT, | ||
occupation VARCHAR, | ||
address VARCHAR | ||
); | ||
``` | ||
- All or any table creations and migrations need to be run before using norm to generate classes | ||
3. Writing query/command and generating classes using norm | ||
- norm needs paths to two folders defined: | ||
1. input source directory - contains all sql files | ||
2. output source directory - would contain all files generated by norm | ||
- add a task in gradle which would execute norm's code generation | ||
``` | ||
task compileNorm(type: JavaExec) { | ||
classpath = configurations.norm | ||
main = "norm.MainKt" | ||
args "${rootProject.rootDir}/sql" //input dir | ||
args "${rootProject.rootDir}/gen" //output dir | ||
args "jdbc:postgresql://localhost/postgres" //postgres connection string with db name | ||
args "postgres" //db username | ||
args "" //db password (optional for local) | ||
} | ||
``` | ||
- write a query eg that fetches all persons whose age is greater than some number | ||
- add a folder in sql folder, say eg ```person``` | ||
- add a sql file in this folder with name, say eg ```get-all-persons-above-given-age``` | ||
```SELECT * FROM persons WHERE AGE > :age;``` | ||
- run ```./gradlew compileNorm```. It will generate a folder within ```gen``` (output dir) with the same name as folder inside ```sql```(input dir), ```person```. | ||
The generated classes would be within a file named - ```GetAllPersonsAboveGivenAge``` (title case name of sql file) | ||
The content of generated file would look like: | ||
``` | ||
package person | ||
import java.sql.PreparedStatement | ||
import java.sql.ResultSet | ||
import kotlin.Int | ||
import kotlin.String | ||
import norm.ParamSetter | ||
import norm.Query | ||
import norm.RowMapper | ||
data class GetAllPersonsAboveGivenAgeParams( | ||
val age: Int? | ||
) | ||
class GetAllPersonsAboveGivenAgeParamSetter : ParamSetter<GetAllPersonsAboveGivenAgeParams> { | ||
override fun map(ps: PreparedStatement, params: GetAllPersonsAboveGivenAgeParams) { | ||
ps.setObject(1, params.age) | ||
} | ||
} | ||
data class GetAllPersonsAboveGivenAgeResult( | ||
val id: Int, | ||
val name: String?, | ||
val age: Int?, | ||
val occupation: String?, | ||
val address: String? | ||
) | ||
class GetAllPersonsAboveGivenAgeRowMapper : RowMapper<GetAllPersonsAboveGivenAgeResult> { | ||
override fun map(rs: ResultSet): GetAllPersonsAboveGivenAgeResult = | ||
GetAllPersonsAboveGivenAgeResult( | ||
id = rs.getObject("id") as kotlin.Int, | ||
name = rs.getObject("name") as kotlin.String?, | ||
age = rs.getObject("age") as kotlin.Int?, | ||
occupation = rs.getObject("occupation") as kotlin.String?, | ||
address = rs.getObject("address") as kotlin.String?) | ||
} | ||
class GetAllPersonsAboveGivenAgeQuery : Query<GetAllPersonsAboveGivenAgeParams, | ||
GetAllPersonsAboveGivenAgeResult> { | ||
override val sql: String = """ | ||
|SELECT * FROM persons WHERE AGE > ?; | ||
|""".trimMargin() | ||
override val mapper: RowMapper<GetAllPersonsAboveGivenAgeResult> = | ||
GetAllPersonsAboveGivenAgeRowMapper() | ||
override val paramSetter: ParamSetter<GetAllPersonsAboveGivenAgeParams> = | ||
GetAllPersonsAboveGivenAgeParamSetter() | ||
} | ||
``` | ||
4. Executing queries/commands using norm | ||
- To run any query/command, a DataSource connection of postgres is required. | ||
- Create an instance of DataSource using the postgresql driver(already added in dependency) methods | ||
``` | ||
val dataSource = PGSimpleDataSource().also { | ||
it.setUrl("jdbc:postgresql://localhost/postgres") | ||
it.user = "postgres" | ||
it.password = "" | ||
} | ||
``` | ||
- Execute the query: | ||
``` | ||
val result = dataSource.connection.use { connection -> | ||
GetAllPersonsAboveGivenAgeQuery().query(connection, GetAllPersonsAboveGivenAgeParams(20)) | ||
} | ||
result.map { res -> | ||
println(res.id) | ||
println(res.name) | ||
println(res.age) | ||
println(res.occupation) | ||
println(res.address) | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,4 +17,4 @@ CREATE TABLE owners( | |
id serial PRIMARY KEY, | ||
colors varchar[], | ||
details jsonb | ||
) | ||
); |