Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
ashwini-desai committed Jun 15, 2020
2 parents 6808dc7 + 5f0bf7d commit 87990f7
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 2 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
# Ignore Gradle build output directory
build

**/out/*
183 changes: 182 additions & 1 deletion README.md
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)
}
```
2 changes: 1 addition & 1 deletion codegen/src/test/resources/init_postgres.sql
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ CREATE TABLE owners(
id serial PRIMARY KEY,
colors varchar[],
details jsonb
)
);

0 comments on commit 87990f7

Please sign in to comment.