Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

default value support #431

Merged
merged 8 commits into from
Sep 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions buildSrc/src/main/kotlin/ktorm.maven-publish.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ publishing {
name.set("Sven Allers")
email.set("sven.allers@gmx.de")
}
developer {
id.set("lookup-cat")
name.set("夜里的向日葵")
email.set("641571835@qq.com")
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.ktorm.support.mysql

import org.ktorm.expression.ScalarExpression
import org.ktorm.schema.Column
import org.ktorm.schema.SqlType
import java.util.Collections.emptyMap

/**
* DefaultValue expression, translated to the `default` keyword in MySQL.
*/
public data class DefaultValueExpression<T : Any>(
override val sqlType: SqlType<T>,
override val isLeafNode: Boolean = true,
override val extraProperties: Map<String, Any> = emptyMap()
) : ScalarExpression<T>()

/**
* DefaultValue expression, translated to the `default` keyword in MySQL.
*/
public fun <T : Any> Column<T>.defaultValue(): DefaultValueExpression<T> = DefaultValueExpression(this.sqlType)
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public open class MySqlFormatter(
override fun <T : Any> visitScalar(expr: ScalarExpression<T>): ScalarExpression<T> {
val result = when (expr) {
is MatchAgainstExpression -> visitMatchAgainst(expr)
is DefaultValueExpression -> visitDefaultValue(expr)
else -> super.visitScalar(expr)
}

Expand Down Expand Up @@ -167,6 +168,11 @@ public open class MySqlFormatter(
write(") ")
return expr
}

protected open fun <T : Any> visitDefaultValue(expr: DefaultValueExpression<T>): DefaultValueExpression<T> {
writeKeyword("default ")
return expr
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

增加新的表达式类型后,除了此处,还需要在 MySqlExpressionVisitor 增加相应的 visit 函数,方便第三方扩展,否则新的表达式会默认派发到 visitUnknown


/**
Expand All @@ -187,6 +193,7 @@ public open class MySqlExpressionVisitor : SqlExpressionVisitor() {
override fun <T : Any> visitScalar(expr: ScalarExpression<T>): ScalarExpression<T> {
val result = when (expr) {
is MatchAgainstExpression -> visitMatchAgainst(expr)
is DefaultValueExpression -> visitDefaultValue(expr)
else -> super.visitScalar(expr)
}

Expand Down Expand Up @@ -263,4 +270,8 @@ public open class MySqlExpressionVisitor : SqlExpressionVisitor() {
return expr.copy(matchColumns = matchColumns)
}
}

protected open fun <T : Any> visitDefaultValue(expr: DefaultValueExpression<T>): DefaultValueExpression<T> {
return expr
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package org.ktorm.support.mysql

import org.junit.Test
import org.ktorm.database.Database
import org.ktorm.dsl.eq
import org.ktorm.dsl.inList
import org.ktorm.dsl.insertAndGenerateKey
import org.ktorm.entity.*
import org.ktorm.schema.Table
import org.ktorm.schema.int
import org.ktorm.schema.varchar
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertNotNull
import kotlin.test.assertNull

class DefaultValueTest : BaseMySqlTest() {

object Users : Table<User>("t_user") {
val id = int("id").primaryKey().bindTo { it.id }
val username = varchar("username").bindTo { it.username }
val age = int("age").bindTo { it.age }
}

interface User : Entity<User> {
var id: Int
var username: String
var age: Int?
}

private val Database.users: EntitySequence<User, Users> get() = this.sequenceOf(Users)

@Test
fun insert() {
val id = database.insertAndGenerateKey(Users) {
set(it.id, it.id.defaultValue())
set(it.username, it.username.defaultValue())
set(it.age, it.age.defaultValue())
}
assertIs<Int>(id)
val entity = database.users.firstOrNull { it.id eq id }
assertNotNull(entity)
assertNotNull(entity.id)
assertEquals(entity.username, "default")
assertNull(entity.age)
}

@Test
fun bulkInsert() {
database.bulkInsert(Users) {
item {
set(it.id, 10)
set(it.username, it.username.defaultValue())
set(it.age, 10)
}
item {
set(it.id, 11)
set(it.username, it.username.defaultValue())
set(it.age, 11)
}
}
val defaultValues = database.users.filter { it.id inList (10..11).toList() }.toList()
assertEquals(defaultValues.size, 2)
for (defaultValue in defaultValues) {
assertEquals(defaultValue.username, "default")
}

database.bulkInsertOrUpdate(Users) {
item {
set(it.id, 10)
set(it.age, 10)
}
onDuplicateKey {
set(it.age, it.age.defaultValue())
}
}
val user = database.users.first { it.id eq 10 }
assertNull(user.age)
}

}
3 changes: 2 additions & 1 deletion ktorm-support-mysql/src/test/resources/drop-mysql-data.sql
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
drop table if exists t_department;
drop table if exists t_employee;
drop table if exists t_employee;
drop table if exists t_user;
6 changes: 6 additions & 0 deletions ktorm-support-mysql/src/test/resources/init-mysql-data.sql
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ create table t_employee(

create fulltext index employee_name_job on t_employee(name, job);

create table t_user(
id int not null primary key auto_increment,
username varchar(128) default 'default',
age int
);

insert into t_department(name, location) values ('tech', 'Guangzhou');
insert into t_department(name, location) values ('finance', 'Beijing');

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2018-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.ktorm.support.postgresql

import org.ktorm.expression.ScalarExpression
import org.ktorm.schema.Column
import org.ktorm.schema.SqlType
import java.util.Collections.emptyMap

/**
* DefaultValue expression, translated to the `default` keyword in PostgreSQL.
*/
public data class DefaultValueExpression<T : Any>(
override val sqlType: SqlType<T>,
override val isLeafNode: Boolean = true,
override val extraProperties: Map<String, Any> = emptyMap()
) : ScalarExpression<T>()

/**
* DefaultValue expression, translated to the `default` keyword in PostgreSQL.
*/
public fun <T : Any> Column<T>.defaultValue(): DefaultValueExpression<T> = DefaultValueExpression(this.sqlType)
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public open class PostgreSqlFormatter(
is ILikeExpression -> visitILike(expr)
is HStoreExpression -> visitHStore(expr)
is CubeExpression -> visitCube(expr)
is DefaultValueExpression -> visitDefaultValue(expr)
else -> super.visitScalar(expr)
}

Expand Down Expand Up @@ -167,6 +168,11 @@ public open class PostgreSqlFormatter(
return expr
}

protected open fun <T : Any> visitDefaultValue(expr: DefaultValueExpression<T>): DefaultValueExpression<T> {
writeKeyword("default ")
return expr
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同理,新的表达式类型需要在 PostgreSqlExpressionVisitor 增加相应的 visit 函数

protected open fun visitInsertOrUpdate(expr: InsertOrUpdateExpression): InsertOrUpdateExpression {
writeKeyword("insert into ")
visitTable(expr.table)
Expand Down Expand Up @@ -259,6 +265,7 @@ public open class PostgreSqlExpressionVisitor : SqlExpressionVisitor() {
is ILikeExpression -> visitILike(expr)
is HStoreExpression -> visitHStore(expr)
is CubeExpression -> visitCube(expr)
is DefaultValueExpression -> visitDefaultValue(expr)
else -> super.visitScalar(expr)
}

Expand Down Expand Up @@ -368,4 +375,8 @@ public open class PostgreSqlExpressionVisitor : SqlExpressionVisitor() {

return if (changed) result else assignments
}

protected open fun <T : Any> visitDefaultValue(expr: DefaultValueExpression<T>): DefaultValueExpression<T> {
return expr
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package org.ktorm.support.postgresql

import org.junit.Test
import org.ktorm.database.Database
import org.ktorm.dsl.eq
import org.ktorm.dsl.inList
import org.ktorm.entity.*
import org.ktorm.schema.Table
import org.ktorm.schema.int
import org.ktorm.schema.varchar
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull

class DefaultValueTest : BasePostgreSqlTest() {

object Users : Table<User>("t_user") {
val id = int("id").primaryKey().bindTo { it.id }
val username = varchar("username").bindTo { it.username }
val age = int("age").bindTo { it.age }
}

interface User : Entity<User> {
var id: Int
var username: String
var age: Int?
}

private val Database.users: EntitySequence<User, Users> get() = this.sequenceOf(Users)

@Test
fun insert() {
val id = database.insertReturning(Users, returning = Users.id) {
set(it.id, it.id.defaultValue())
set(it.username, it.username.defaultValue())
set(it.age, it.age.defaultValue())
}
assertNotNull(id)
val entity = database.users.firstOrNull { it.id eq id }
assertNotNull(entity)
assertNotNull(entity.id)
assertEquals(entity.username, "default")
assertNull(entity.age)
}

@Test
fun bulkInsert() {
database.bulkInsert(Users) {
item {
set(it.id, 10)
set(it.username, it.username.defaultValue())
set(it.age, 10)
}
item {
set(it.id, 11)
set(it.username, it.username.defaultValue())
set(it.age, 11)
}
}
val defaultValues = database.users.filter { it.id inList (10..11).toList() }.toList()
assertEquals(defaultValues.size, 2)
for (defaultValue in defaultValues) {
assertEquals(defaultValue.username, "default")
}

database.bulkInsertOrUpdate(Users) {
item {
set(it.id, 10)
set(it.age, 10)
}
onConflict(it.id) {
set(it.age, it.age.defaultValue())
}
}
val user = database.users.first { it.id eq 10 }
assertNull(user.age)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ drop table if exists t_enum;
drop type if exists mood;
drop table if exists t_json;
drop table t_earthdistance;
drop table if exists t_user;
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ create table t_json (obj json, arr json);

create table t_earthdistance(earth_field earth, cube_field cube);

create table t_user(
id serial primary key,
username varchar default 'default',
age int
);

insert into t_department(name, location, "mixedCase") values ('tech', 'Guangzhou', 'one');
insert into t_department(name, location, "mixedCase") values ('finance', 'Beijing', 'two');

Expand All @@ -51,4 +57,4 @@ insert into t_metadata(attrs, numbers)
values ('a=>1, b=>2, c=>NULL'::hstore, array['a', 'b', 'c']);

insert into t_enum(current_mood)
values ('HAPPY')
values ('HAPPY')