This commit is contained in:
tursom 2021-04-12 13:55:42 +08:00
parent 887f04bb06
commit 720e7ceddf
53 changed files with 3471 additions and 6 deletions

View File

@ -20,6 +20,8 @@ include("ts-web")
include("ts-web:ts-web-netty")
include("ts-web:ts-web-coroutine")
include("ts-database")
include("ts-database:ts-mongodb")
include("ts-database:ts-mongodb:ts-mongodb-spring")
//include("web", "aop", "database", "utils", "utils:xml", "utils:async-http", "web:netty-web")
//include("socket", "socket:socket-async")
//include("AsyncSocket")

View File

@ -0,0 +1,20 @@
package cn.tursom.core.datastruct
interface AsyncIterator<T> {
/**
* Returns the next element in the iteration.
*/
suspend operator fun next(): T
/**
* Returns `true` if the iteration has more elements.
*/
suspend operator fun hasNext(): Boolean
}
suspend inline fun <T> AsyncIterator<T>.forEach(action: (T) -> Unit) {
while (hasNext()) {
val element = next()
action(element)
}
}

View File

@ -1,12 +1,10 @@
package cn.tursom.database
import cn.tursom.core.clone.Property
import cn.tursom.core.clone.inject
import cn.tursom.core.clone.instance
import cn.tursom.core.uncheckedCast
import com.baomidou.mybatisplus.annotation.TableField
import com.ddbes.kotlin.clone.Property
import com.ddbes.kotlin.clone.inject
import com.ddbes.kotlin.clone.instance
import com.ddbes.kotlin.jdbc.simpTableField
import com.ddbes.kotlin.jdbc.tableName
import com.ddbes.kotlin.uncheckedCast
import me.liuwj.ktorm.dsl.QueryRowSet
import me.liuwj.ktorm.schema.BaseTable
import me.liuwj.ktorm.schema.Column

View File

@ -0,0 +1,36 @@
plugins {
kotlin("jvm")
`maven-publish`
}
dependencies {
api(project(":"))
implementation(project(":ts-core"))
implementation(project(":ts-core:ts-datastruct"))
implementation(project(":ts-core:ts-log"))
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2")
api(group = "org.mongodb", name = "mongodb-driver-reactivestreams", version = "4.0.5")
}
@kotlin.Suppress("UNCHECKED_CAST")
(rootProject.ext["excludeTest"] as (Project, TaskContainer) -> Unit)(project, tasks)
tasks.register("install") {
finalizedBy(tasks["publishToMavenLocal"])
}
publishing {
publications {
create<MavenPublication>("maven") {
groupId = project.group.toString()
artifactId = project.name
version = project.version.toString()
from(components["java"])
try {
artifact(tasks["sourcesJar"])
} catch (e: Exception) {
}
}
}
}

View File

@ -0,0 +1,111 @@
package cn.tursom.database.mongodb
import com.mongodb.MongoNamespace
import com.mongodb.client.model.*
import org.bson.conversions.Bson
import kotlin.reflect.KProperty1
@Suppress("unused")
object Aggregate {
operator fun invoke(action: Aggregate.() -> Bson) = this.action()
fun addFields(vararg fields: Field<*>): Bson = Aggregates.addFields(fields.asList())
fun addFields(fields: List<Field<*>>): Bson? = Aggregates.addFields(fields)
fun <TExpression, Boundary> bucket(
groupBy: TExpression,
boundaries: List<Boundary>
): Bson = Aggregates.bucket(groupBy, boundaries)
fun <TExpression, TBoundary> bucket(
groupBy: TExpression,
boundaries: List<TBoundary>,
options: BucketOptions
): Bson = Aggregates.bucket(groupBy, boundaries, options)
fun <TExpression> bucketAuto(groupBy: TExpression, buckets: Int): Bson = Aggregates.bucketAuto(groupBy, buckets)
fun <TExpression> bucketAuto(
groupBy: TExpression,
buckets: Int,
options: BucketAutoOptions
): Bson = Aggregates.bucketAuto(groupBy, buckets, options)
fun count(): Bson = Aggregates.count()
fun count(field: String): Bson = Aggregates.count(field)
fun match(filter: Bson): Bson = Aggregates.match(filter)
fun match(value: Any): Bson = Aggregates.match(MongoUtil.convertToBson(value))
fun project(projection: Bson): Bson = Aggregates.project(projection)
fun sort(sort: Bson): Bson = Aggregates.sort(sort)
fun <TExpression> sortByCount(filter: TExpression): Bson = Aggregates.sortByCount(filter)
fun skip(skip: Int): Bson = Aggregates.skip(skip)
fun limit(limit: Int): Bson = Aggregates.limit(limit)
fun lookup(
from: String,
localField: String,
foreignField: String,
`as`: String
): Bson = Aggregates.lookup(from, localField, foreignField, `as`)
fun lookup(from: String, pipeline: List<Bson?>, `as`: String): Bson = Aggregates.lookup(from, pipeline, `as`)
fun <TExpression> lookup(
from: String,
let: List<Variable<TExpression>>? = null,
pipeline: List<Bson>,
`as`: String
): Bson = Aggregates.lookup(from, let, pipeline, `as`)
fun facet(facets: List<Facet>): Bson = Aggregates.facet(facets)
fun facet(vararg facets: Facet): Bson = Aggregates.facet(facets.asList())
fun <TExpression> graphLookup(
from: String,
startWith: TExpression,
connectFromField: String,
connectToField: String,
`as`: String
): Bson = Aggregates.graphLookup(from, startWith, connectFromField, connectToField, `as`)
fun <TExpression> graphLookup(
from: String,
startWith: TExpression,
connectFromField: String,
connectToField: String,
`as`: String,
options: GraphLookupOptions
): Bson = Aggregates.graphLookup(from, startWith, connectFromField, connectToField, `as`, options)
fun <TExpression> group(
id: TExpression? = null,
vararg fieldAccumulators: BsonField
): Bson = Aggregates.group(id, fieldAccumulators.asList())
fun <TExpression> group(
id: TExpression? = null,
fieldAccumulators: List<BsonField>
): Bson = Aggregates.group(id, fieldAccumulators)
fun unwind(field: KProperty1<*, *>): Bson = Aggregates.unwind(MongoUtil.fieldName(field))
fun unwind(field: KProperty1<*, *>, unwindOptions: UnwindOptions): Bson =
Aggregates.unwind(MongoUtil.fieldName(field), unwindOptions)
fun unwind(fieldName: String): Bson = Aggregates.unwind(fieldName)
fun unwind(fieldName: String, unwindOptions: UnwindOptions): Bson = Aggregates.unwind(fieldName, unwindOptions)
fun out(collectionName: String): Bson = Aggregates.out(collectionName)
fun merge(collectionName: String): Bson = Aggregates.merge(collectionName)
fun merge(namespace: MongoNamespace): Bson = Aggregates.merge(namespace)
fun merge(collectionName: String, options: MergeOptions): Bson = Aggregates.merge(collectionName, options)
fun merge(namespace: MongoNamespace, options: MergeOptions): Bson = Aggregates.merge(namespace, options)
fun <TExpression> replaceRoot(value: TExpression): Bson = Aggregates.replaceRoot(value)
fun <TExpression> replaceWith(value: TExpression): Bson = Aggregates.replaceWith(value)
fun sample(size: Int): Bson = Aggregates.sample(size)
}

View File

@ -0,0 +1,28 @@
package cn.tursom.database.mongodb
import cn.tursom.core.Parser
import org.bson.Document
interface BsonFactory<T> {
val clazz: Class<T>
fun parse(document: Document) = Parser.parse(document, clazz)!!
fun convertToBson(entity: T): Document
//fun convertToEntity(bson: Document): T
companion object {
operator fun <T> get(clazz: Class<T>): BsonFactory<T> {
return BsonFactoryImpl(clazz)
}
inline fun <reified T : Any?> get(): BsonFactory<T> {
return get(T::class.java)
}
fun convertToBson(entity: Any): Document {
return get(entity.javaClass).convertToBson(entity)
}
}
}

View File

@ -0,0 +1,25 @@
package cn.tursom.database.mongodb
import cn.tursom.core.isStatic
import cn.tursom.core.isTransient
import cn.tursom.database.mongodb.annotation.Ignore
import org.bson.Document
open class BsonFactoryImpl<T>(final override val clazz: Class<T>) : BsonFactory<T> {
private val fields = clazz.declaredFields.filter {
it.isAccessible = true
!it.isStatic() && !it.isTransient() && it.getAnnotation(Ignore::class.java) == null
}
override fun convertToBson(entity: T): Document {
val bson = Document()
fields.forEach {
MongoUtil.injectValue(bson, it.get(entity) ?: return@forEach, it)
}
return bson
}
override fun toString(): String {
return "BsonFactoryImpl(clazz=$clazz, fields=$fields)"
}
}

View File

@ -0,0 +1,136 @@
package cn.tursom.database.mongodb
import com.mongodb.client.model.Collation
import com.mongodb.client.model.IndexOptions
import org.bson.Document
import org.bson.conversions.Bson
import java.lang.reflect.Field
import java.util.concurrent.TimeUnit
import kotlin.reflect.KProperty
@Suppress("unused")
class IndexBuilder {
val indexDocument = Document()
val indexOption = IndexOptions()
var background
get() = indexOption.isBackground
set(value) {
indexOption.background(value)
}
var unique
get() = indexOption.isUnique
set(value) {
indexOption.unique(value)
}
var name: String?
get() = indexOption.name
set(value) {
indexOption.name(value)
}
var sparse
get() = indexOption.isSparse
set(value) {
indexOption.sparse(value)
}
var expireAfterSeconds: Long?
get() = indexOption.getExpireAfter(TimeUnit.SECONDS)
set(value) {
indexOption.expireAfter(value, TimeUnit.SECONDS)
}
fun getExpireAfter(timeUnit: TimeUnit): Long? = indexOption.getExpireAfter(timeUnit)
fun expireAfter(expireAfter: Long?, timeUnit: TimeUnit): IndexOptions? =
indexOption.expireAfter(expireAfter, timeUnit)
var version: Int?
get() = indexOption.version
set(value) {
indexOption.version(value)
}
var weights: Bson?
get() = indexOption.weights
set(value) {
indexOption.weights(value)
}
var defaultLanguage: String?
get() = indexOption.defaultLanguage
set(value) {
indexOption.defaultLanguage(value)
}
var languageOverride: String?
get() = indexOption.languageOverride
set(value) {
indexOption.languageOverride(value)
}
var textVersion: Int?
get() = indexOption.textVersion
set(value) {
indexOption.textVersion(value)
}
var sphereVersion: Int?
get() = indexOption.sphereVersion
set(value) {
indexOption.sphereVersion(value)
}
var bits: Int?
get() = indexOption.bits
set(value) {
indexOption.bits(value)
}
var min: Double?
get() = indexOption.min
set(value) {
indexOption.min(value)
}
var max: Double?
get() = indexOption.max
set(value) {
indexOption.max(value)
}
var bucketSize: Double?
get() = indexOption.bucketSize
set(value) {
indexOption.bucketSize(value)
}
var storageEngine: Bson?
get() = indexOption.storageEngine
set(value) {
indexOption.storageEngine(value)
}
var partialFilterExpression: Bson?
get() = indexOption.partialFilterExpression
set(value) {
indexOption.partialFilterExpression(value)
}
var collation: Collation?
get() = indexOption.collation
set(value) {
indexOption.collation(value)
}
var wildcardProjection: Bson
get() = indexOption.wildcardProjection
set(value) {
indexOption.wildcardProjection(value)
}
enum class SortEnum(val code: Int) {
DSC(1), DESC(-1)
}
infix fun String.index(sort: SortEnum) {
indexDocument[this] = sort.code
}
operator fun String.unaryPlus() = this index SortEnum.DSC
operator fun String.unaryMinus() = this index SortEnum.DESC
infix fun KProperty<*>.index(sort: SortEnum) = MongoUtil.fieldName(this) index sort
operator fun KProperty<*>.unaryPlus() = +MongoUtil.fieldName(this)
operator fun KProperty<*>.unaryMinus() = -MongoUtil.fieldName(this)
infix fun Field.index(sort: SortEnum) = MongoUtil.fieldName(this) index sort
operator fun Field.unaryPlus() = +MongoUtil.fieldName(this)
operator fun Field.unaryMinus() = -MongoUtil.fieldName(this)
}

View File

@ -0,0 +1,27 @@
package cn.tursom.database.mongodb
import org.bson.Document
import java.lang.reflect.Field
import kotlin.reflect.KProperty
interface MongoName {
val Class<*>.collectionName: String get() = MongoUtil.collectionName(this)
val Field.fieldName: String get() = MongoUtil.fieldName(this)
val KProperty<*>.fieldName get() = MongoUtil.fieldName(this)
val Any.convertToBson: Document get() = MongoUtil.convertToBson(this)
operator fun String.rem(field: String) = "$this.$field"
operator fun String.rem(field: KProperty<*>) = "$this.${field.fieldName}"
operator fun String.rem(field: Field) = "$this.${field.fieldName}"
operator fun KProperty<*>.rem(field: String) = "$fieldName.$field"
operator fun KProperty<*>.rem(field: KProperty<*>) = "$fieldName.${field.fieldName}"
operator fun KProperty<*>.rem(field: Field) = "$fieldName.${field.fieldName}"
operator fun Field.rem(field: String) = "$fieldName.$field"
operator fun Field.rem(field: KProperty<*>) = "$fieldName.${field.fieldName}"
operator fun Field.rem(field: Field) = "$fieldName.${field.fieldName}"
}

View File

@ -0,0 +1,87 @@
package cn.tursom.database.mongodb
import cn.tursom.core.uncheckedCast
import cn.tursom.database.mongodb.operator.MongoOperator
import cn.tursom.database.mongodb.operator.MongoOperatorImpl
import com.mongodb.MongoClientSettings
import com.mongodb.ServerAddress
import com.mongodb.client.model.InsertManyOptions
import com.mongodb.client.model.InsertOneOptions
import com.mongodb.client.model.UpdateOptions
import com.mongodb.reactivestreams.client.MongoClient
import com.mongodb.reactivestreams.client.MongoClients
import com.mongodb.reactivestreams.client.MongoDatabase
import org.bson.conversions.Bson
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executor
class MongoTemplate(
private val db: MongoDatabase,
val subscribeExecutor: Executor = MongoUtil.mongoExecutor,
) : MongoDatabase by db {
constructor(
mongoClient: MongoClient,
db: String,
subscribeExecutor: Executor = MongoUtil.mongoExecutor,
) : this(mongoClient.getDatabase(db), subscribeExecutor)
constructor(
db: String,
mongoClientSettingsBuilder: MongoClientSettings.Builder,
subscribeExecutor: Executor = MongoUtil.mongoExecutor,
) : this(MongoClients.create(mongoClientSettingsBuilder.build()), db, subscribeExecutor)
constructor(
db: String,
vararg servers: ServerAddress,
subscribeExecutor: Executor = MongoUtil.mongoExecutor,
) : this(
db,
MongoClientSettings.builder().also {
it.applyToClusterSettings { builder ->
builder.hosts(servers.asList())
}
},
subscribeExecutor
)
constructor(host: String, port: Int, db: String) : this(db, ServerAddress(host, port))
private val operatorMap = ConcurrentHashMap<Class<*>, MongoOperatorImpl<*>>()
suspend fun <T : Any> save(entity: T, options: InsertOneOptions = InsertOneOptions()) =
getCollection(entity.javaClass).save(entity, options)
suspend inline fun <reified T : Any> save(entities: Collection<T>, options: InsertManyOptions = InsertManyOptions()) =
getCollection<T>().save(entities, options)
suspend fun <T : Any> update(entity: T, where: Bson, options: UpdateOptions = UpdateOptions()) =
getCollection(entity.javaClass).update(entity, where, options)
@Suppress("SpellCheckingInspection")
suspend fun <T : Any> upsert(entity: T, where: Bson, options: UpdateOptions = UpdateOptions()) =
getCollection(entity.javaClass).upsert(entity, where, options)
inline fun <reified T : Any> getCollection(): MongoOperator<T> = getCollection(T::class.java)
fun <T : Any> getCollection(clazz: Class<T>): MongoOperator<T> {
return operatorMap[clazz]?.uncheckedCast() ?: run {
val operator = MongoOperatorImpl(clazz, db, subscribeExecutor)
operatorMap[clazz] = operator
operator
}
}
inline fun <reified T : Any> getOperator(collection: String) = getOperator(collection, T::class.java)
fun <T : Any> getOperator(collection: String, clazz: Class<T>): MongoOperator<T> {
return MongoOperatorImpl(getCollection(collection), clazz, subscribeExecutor)
}
override fun toString(): String {
return "MongoTemplate(db=$db)"
}
//override fun close() {
// //mongoClient.close()
//}
}

View File

@ -0,0 +1,117 @@
package cn.tursom.database.mongodb
import cn.tursom.core.isStatic
import cn.tursom.core.isTransient
import cn.tursom.core.uncheckedCast
import cn.tursom.database.mongodb.annotation.Collection
import cn.tursom.database.mongodb.annotation.Ignore
import org.bson.BsonValue
import org.bson.Document
import org.bson.conversions.Bson
import java.lang.reflect.Field
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.LinkedTransferQueue
import java.util.concurrent.ThreadPoolExecutor
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import kotlin.math.min
import kotlin.reflect.KProperty
import kotlin.reflect.jvm.javaField
object MongoUtil {
private val fieldNameCache = ConcurrentHashMap<Field, String>()
private val mongoThreadId = AtomicInteger(0)
val mongoExecutor = getThreadPool(min(4, Runtime.getRuntime().availableProcessors()))
fun collectionName(clazz: Class<*>): String {
return clazz.getAnnotation(Collection::class.java)?.name ?: clazz.simpleName
}
fun fieldName(field: Field): String {
var fieldName = fieldNameCache[field]
if (fieldName == null) {
fieldName = field.getAnnotation(cn.tursom.database.mongodb.annotation.Field::class.java)?.name ?: field.name
fieldNameCache[field] = fieldName!!
}
return fieldName
}
fun fieldName(property: KProperty<*>): String {
val javaField = property.javaField
return if (javaField != null) fieldName(javaField) else property.name
}
fun convertToBson(entity: Any): Document {
return when (entity) {
is Map<*, *> -> entity.convert()
else -> {
val bson = Document()
entity.javaClass.declaredFields.filter {
it.isAccessible = true
!it.isStatic()
&& !it.isTransient()
&& it.getAnnotation(Ignore::class.java) == null
&& (it.type != Lazy::class.java || it.get(entity).uncheckedCast<Lazy<*>>().isInitialized())
}.forEach {
injectValue(bson, it.get(entity) ?: return@forEach, it)
}
bson
}
}
}
fun injectValue(bson: Document, value: Any, field: Field) {
when (value) {
is Pair<*, *> -> bson[value.first?.toString() ?: return] = convertToBson(value.second ?: return)
is Map.Entry<*, *> -> bson[value.key?.toString() ?: return] =
convertToBson(value.value ?: return)
else -> bson[fieldName(field)] = value.convert() ?: return
}
}
private fun Iterator<*>.convert(): List<*> {
val list = ArrayList<Any?>()
forEach {
list.add(it.convert() ?: return@forEach)
}
return list
}
private fun Map<*, *>.convert(): Document {
val doc = Document()
forEach { any, u ->
any ?: return@forEach
doc[any.toString()] = u.convert() ?: return@forEach
}
return doc
}
private fun Any?.convert() = when (this) {
null -> null
is Enum<*> -> name
is Boolean, is Number, is String, is Bson, is BsonValue, is ByteArray -> this
is ShortArray -> asList()
is IntArray -> asList()
is LongArray -> asList()
is FloatArray -> asList()
is DoubleArray -> asList()
is BooleanArray -> asList()
is Map<*, *> -> this.convert()
is Iterator<*> -> this.convert()
is Iterable<*> -> this.iterator().convert()
else -> convertToBson(this)
}
private fun getThreadPool(nThreads: Int) = ThreadPoolExecutor(
nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
LinkedTransferQueue(),
{
val thread = Thread(it)
thread.isDaemon = true
thread.name = "mongo-worker-${mongoThreadId.incrementAndGet()}"
thread
},
ThreadPoolExecutor.CallerRunsPolicy()
)
}

View File

@ -0,0 +1,55 @@
package cn.tursom.database.mongodb
import com.mongodb.client.model.Projections
import org.bson.conversions.Bson
import kotlin.reflect.KProperty1
object Projection {
operator fun invoke(action: Projection.() -> Bson) = this.action()
fun <T, TExpression> computed(field: KProperty1<out T, *>, expression: TExpression): Bson =
computed(MongoUtil.fieldName(field), expression)
fun <TExpression> computed(fieldName: String, expression: TExpression): Bson =
Projections.computed(fieldName, expression)
fun <T> include(vararg fieldNames: KProperty1<out T, *>): Bson =
Projections.include(fieldNames.map { MongoUtil.fieldName(it) })
fun <T> include(fieldNames: Collection<KProperty1<out T, *>>): Bson =
Projections.include(fieldNames.map { MongoUtil.fieldName(it) })
fun include(vararg fieldNames: String): Bson = Projections.include(fieldNames.asList())
fun include(fieldNames: List<String>): Bson = Projections.include(fieldNames)
fun <T> exclude(vararg fieldNames: KProperty1<out T, *>): Bson =
Projections.exclude(fieldNames.map { MongoUtil.fieldName(it) })
fun <T> exclude(fieldNames: Collection<KProperty1<out T, *>>): Bson =
Projections.exclude(fieldNames.map { MongoUtil.fieldName(it) })
fun exclude(vararg fieldNames: String): Bson = Projections.exclude(fieldNames.asList())
fun exclude(fieldNames: List<String>): Bson = Projections.exclude(fieldNames)
fun excludeId(): Bson = Projections.excludeId()
fun <T> exclude(field: KProperty1<out T, *>): Bson = Projections.elemMatch(MongoUtil.fieldName(field))
fun <T> exclude(field: KProperty1<out T, *>, filter: Bson): Bson =
Projections.elemMatch(MongoUtil.fieldName(field), filter)
fun elemMatch(fieldName: String): Bson = Projections.elemMatch(fieldName)
fun elemMatch(fieldName: String, filter: Bson): Bson = Projections.elemMatch(fieldName, filter)
fun <T> metaTextScore(field: KProperty1<out T, *>): Bson = Projections.metaTextScore(MongoUtil.fieldName(field))
fun metaTextScore(fieldName: String): Bson = Projections.metaTextScore(fieldName)
fun <T> slice(field: KProperty1<out T, *>, limit: Int): Bson = Projections.slice(MongoUtil.fieldName(field), limit)
fun <T> slice(field: KProperty1<out T, *>, skip: Int, limit: Int): Bson =
Projections.slice(MongoUtil.fieldName(field), skip, limit)
fun slice(fieldName: String, limit: Int): Bson = Projections.slice(fieldName, limit)
fun slice(fieldName: String, skip: Int, limit: Int): Bson = Projections.slice(fieldName, skip, limit)
fun fields(vararg projections: Bson): Bson = Projections.fields(projections.asList())
fun fields(projections: List<Bson>): Bson = Projections.fields(projections)
}

View File

@ -0,0 +1,30 @@
package cn.tursom.database.mongodb
import com.mongodb.client.model.Sorts
import org.bson.conversions.Bson
import kotlin.reflect.KProperty1
object Sort {
operator fun invoke(action: Sort.() -> Bson) = this.action()
fun <T> ascending(vararg fieldNames: KProperty1<out T, *>): Bson =
Sorts.ascending(fieldNames.map { MongoUtil.fieldName(it) })
fun <T> ascending(fieldNames: Collection<KProperty1<out T, *>>): Bson =
Sorts.ascending(fieldNames.map { MongoUtil.fieldName(it) })
fun ascending(vararg fieldNames: String): Bson = Sorts.ascending(fieldNames.asList())
fun ascending(fieldNames: List<String>): Bson = Sorts.ascending(fieldNames)
fun <T> descending(vararg fieldNames: KProperty1<out T, *>): Bson =
Sorts.descending(fieldNames.map { MongoUtil.fieldName(it) })
fun <T> descending(fieldNames: Collection<KProperty1<out T, *>>): Bson =
Sorts.descending(fieldNames.map { MongoUtil.fieldName(it) })
fun descending(vararg fieldNames: String): Bson = Sorts.descending(fieldNames.asList())
fun descending(fieldNames: List<String>): Bson = Sorts.descending(fieldNames)
fun <T> KProperty1<out T, *>.metaTextScore(): Bson = Sorts.metaTextScore(MongoUtil.fieldName(this))
fun metaTextScore(fieldName: String): Bson = Sorts.metaTextScore(fieldName)
fun orderBy(vararg sorts: Bson): Bson = Sorts.orderBy(sorts.asList())
fun orderBy(sorts: List<Bson>): Bson = Sorts.orderBy(sorts)
}

View File

@ -0,0 +1,34 @@
package cn.tursom.database.mongodb
import com.mongodb.client.model.Updates
import org.bson.conversions.Bson
import kotlin.reflect.KProperty
object Update : MongoName {
operator fun invoke(action: Update.() -> Bson) = this.action()
fun and(vararg update: Bson): Bson = Updates.combine(*update)
fun or(vararg update: Bson): Bson = Updates.combine(*update)
infix fun Bson.and(update: Bson): Bson = Updates.combine(this, update)
infix fun Bson.and(update: Update.() -> Bson): Bson = and(Update.update())
fun combine(vararg updates: Bson): Bson = Updates.combine(*updates)
infix fun String.set(value: Any): Bson = when (value) {
is String, is Number, is Boolean -> Updates.set(this, value)
else -> Updates.set(this, MongoUtil.convertToBson(value))
}
infix fun KProperty<*>.set(value: Any): Bson = MongoUtil.fieldName(this) set value
fun KProperty<*>.unset(): Bson = Updates.unset(MongoUtil.fieldName(this))
fun setOnInsert(value: Bson): Bson = Updates.setOnInsert(value)
fun setOnInsert(value: Any): Bson = Updates.setOnInsert(MongoUtil.convertToBson(value))
infix fun KProperty<*>.setOnInsert(value: Any): Bson = Updates.setOnInsert(MongoUtil.fieldName(this), value)
infix fun KProperty<*>.rename(value: String): Bson = Updates.rename(MongoUtil.fieldName(this), value)
infix fun String.rename(value: String): Bson = Updates.rename(this, value)
infix fun KProperty<*>.inc(value: Number): Bson = Updates.inc(MongoUtil.fieldName(this), value)
infix fun KProperty<*>.mul(value: Number): Bson = Updates.mul(MongoUtil.fieldName(this), value)
infix fun KProperty<*>.min(value: Any): Bson = Updates.min(MongoUtil.fieldName(this), value)
infix fun KProperty<*>.max(value: Any): Bson = Updates.max(MongoUtil.fieldName(this), value)
}

View File

@ -0,0 +1,75 @@
package cn.tursom.database.mongodb
import com.mongodb.client.model.Filters
import com.mongodb.client.model.TextSearchOptions
import org.bson.BsonType
import org.bson.conversions.Bson
import kotlin.reflect.KProperty
@Suppress("unused")
object Where : MongoName {
inline operator fun invoke(action: Where.() -> Bson) = this.action()
infix fun String.eq(value: Any): Bson = Filters.eq(this, value)
infix fun String.ne(value: Any): Bson = Filters.ne(this, value)
infix fun String.gt(value: Any): Bson = Filters.gt(this, value)
infix fun String.lt(value: Any): Bson = Filters.lt(this, value)
infix fun String.gte(value: Any): Bson = Filters.gte(this, value)
infix fun String.lte(value: Any): Bson = Filters.lte(this, value)
fun String.`in`(vararg value: Any): Bson = Filters.`in`(this, *value)
fun String.nin(vararg value: Any): Bson = Filters.nin(this, *value)
fun String.all(vararg value: Any): Bson = Filters.all(this, *value)
infix fun String.all(value: Iterable<Any>): Bson = Filters.all(this, value)
fun String.exists(exists: Boolean = true): Bson = Filters.exists(this, exists)
infix fun String.type(type: String): Bson = Filters.type(this, type)
infix fun String.type(type: BsonType): Bson = Filters.type(this, type)
fun String.mod(divisor: Long, remainder: Long): Bson = Filters.mod(this, divisor, remainder)
infix fun String.regex(regex: String): Bson = Filters.regex(this, regex)
infix fun String.regex(regex: Regex): Bson = Filters.regex(this, regex.pattern)
fun String.regex(regex: String, option: String): Bson = Filters.regex(this, regex, option)
fun String.regex(regex: Regex, option: String): Bson = Filters.regex(this, regex.pattern, option)
infix fun String.elemMatch(where: Bson): Bson = Filters.elemMatch(this, where)
infix fun String.elemMatch(where: Where.() -> Bson): Bson = Filters.elemMatch(this, Where.where())
infix fun KProperty<*>.eq(value: Any): Bson = Filters.eq(MongoUtil.fieldName(this), value)
infix fun KProperty<*>.ne(value: Any): Bson = Filters.ne(MongoUtil.fieldName(this), value)
infix fun KProperty<*>.gt(value: Any): Bson = Filters.gt(MongoUtil.fieldName(this), value)
infix fun KProperty<*>.lt(value: Any): Bson = Filters.lt(MongoUtil.fieldName(this), value)
infix fun KProperty<*>.gte(value: Any): Bson = Filters.gte(MongoUtil.fieldName(this), value)
infix fun KProperty<*>.lte(value: Any): Bson = Filters.lte(MongoUtil.fieldName(this), value)
fun KProperty<*>.`in`(vararg value: Any): Bson = Filters.`in`(MongoUtil.fieldName(this), *value)
fun KProperty<*>.nin(vararg value: Any): Bson = Filters.nin(MongoUtil.fieldName(this), *value)
fun KProperty<*>.all(vararg value: Any): Bson = Filters.all(MongoUtil.fieldName(this), *value)
infix fun KProperty<*>.all(value: Iterable<Any>): Bson = Filters.all(MongoUtil.fieldName(this), value)
fun KProperty<*>.exists(exists: Boolean = true): Bson = Filters.exists(MongoUtil.fieldName(this), exists)
infix fun KProperty<*>.type(type: String): Bson = Filters.type(MongoUtil.fieldName(this), type)
infix fun KProperty<*>.type(type: BsonType): Bson = Filters.type(MongoUtil.fieldName(this), type)
fun KProperty<*>.mod(divisor: Long, remainder: Long): Bson =
Filters.mod(MongoUtil.fieldName(this), divisor, remainder)
infix fun KProperty<*>.regex(regex: String): Bson = Filters.regex(MongoUtil.fieldName(this), regex)
infix fun KProperty<*>.regex(regex: Regex): Bson = Filters.regex(MongoUtil.fieldName(this), regex.pattern)
fun KProperty<*>.regex(regex: String, option: String): Bson = Filters.regex(MongoUtil.fieldName(this), regex, option)
fun KProperty<*>.regex(regex: Regex, option: String): Bson =
Filters.regex(MongoUtil.fieldName(this), regex.pattern, option)
infix fun KProperty<*>.elemMatch(where: Bson): Bson = MongoUtil.fieldName(this) elemMatch where
infix fun KProperty<*>.elemMatch(where: Where.() -> Bson): Bson = MongoUtil.fieldName(this) elemMatch where
fun text(search: String): Bson = Filters.text(search)
fun text(search: String, option: TextSearchOptions): Bson = Filters.text(search, option)
fun where(javaScriptExpression: String): Bson = Filters.where(javaScriptExpression)
fun expr(expression: Any): Bson = Filters.expr(expression)
fun and(vararg bson: Bson): Bson = Filters.and(*bson)
infix fun Bson.and(bson: Bson): Bson = Filters.and(this, bson)
fun or(vararg bson: Bson): Bson = Filters.or(*bson)
infix fun Bson.or(bson: Bson): Bson = Filters.or(this, bson)
fun nor(vararg bson: Bson): Bson = Filters.or(*bson)
infix fun Bson.nor(bson: Bson): Bson = Filters.nor(this, bson)
operator fun Bson.not(): Bson = Filters.not(this)
}

View File

@ -0,0 +1,6 @@
package cn.tursom.database.mongodb.annotation
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class Collection(val name: String)

View File

@ -0,0 +1,6 @@
package cn.tursom.database.mongodb.annotation
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD)
annotation class Field(val name: String)

View File

@ -0,0 +1,5 @@
package cn.tursom.database.mongodb.annotation
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FIELD)
annotation class Ignore(val name: String)

View File

@ -0,0 +1,95 @@
package cn.tursom.database.mongodb.operator
import cn.tursom.log.Slf4j
import cn.tursom.log.impl.Slf4jImpl
import com.mongodb.client.model.InsertManyOptions
import com.mongodb.client.model.InsertOneOptions
import com.mongodb.client.result.InsertOneResult
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ClosedReceiveChannelException
import org.bson.Document
import java.util.concurrent.atomic.AtomicInteger
class AsyncInsertMongoOperator<T : Any>(
private val mongoOperator: MongoOperator<T>,
) : MongoOperator<T> by mongoOperator {
companion object : Slf4j by Slf4jImpl() {
private val acknowledged = InsertOneResult.acknowledged(null)
}
private val wroteAtomic = AtomicInteger(0)
private val documentChannel: Channel<Document> = Channel(1024 * 256)
val wrote get() = wroteAtomic.get()
private val job = GlobalScope.launch {
repeat(32) {
launch {
var list = ArrayList<Document>()
var lastSave = System.currentTimeMillis()
suspend fun save() {
if (list.isEmpty()) return
if (logger.isDebugEnabled) {
logger.debug("begin to save document: {}", list)
}
var saveDocument = mongoOperator.saveDocument(list, InsertManyOptions().ordered(false))
var retryTimes = 0
while (saveDocument?.wasAcknowledged() != true && retryTimes < 3) {
retryTimes++
delay(100)
saveDocument = mongoOperator.saveDocument(list)
}
list = ArrayList()
lastSave = System.currentTimeMillis()
saveDocument ?: return
wroteAtomic.addAndGet(saveDocument.insertedIds.size)
if (logger.isDebugEnabled) {
logger.debug("save result: {}", saveDocument)
}
}
@Suppress("EXPERIMENTAL_API_USAGE")
while (!documentChannel.isClosedForReceive) {
try {
list.add(withTimeout(100) { documentChannel.receive() })
} catch (e: TimeoutCancellationException) {
} catch (e: ClosedReceiveChannelException) {
break
}
if (list.size >= 1024 || System.currentTimeMillis() - lastSave > 100) {
save()
}
}
if (list.size > 0) {
save()
}
}
}
}
override suspend fun save(entity: T, options: InsertOneOptions): Boolean {
//insertOne(entity, options)
if (logger.isDebugEnabled) {
logger.debug("save object: {}, options: {}, result: {}", entity, options, insertOne(entity, options))
}
return true
}
override suspend fun saveDocument(document: Document, options: InsertOneOptions): InsertOneResult? {
documentChannel.send(document)
return acknowledged
}
override suspend fun insertOne(entity: T, options: InsertOneOptions): InsertOneResult? {
documentChannel.send(convertToBson(entity))
return acknowledged
}
override fun close() {
documentChannel.close()
}
suspend fun join() {
job.join()
}
}

View File

@ -0,0 +1,104 @@
package cn.tursom.database.mongodb.operator
import com.mongodb.client.model.InsertManyOptions
import com.mongodb.client.model.InsertOneOptions
import com.mongodb.client.result.InsertOneResult
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ClosedReceiveChannelException
import org.bson.Document
import java.util.concurrent.atomic.AtomicInteger
class CachedInsertMongoOperator<T : Any>(
private val mongoOperator: MongoOperator<T>,
private val ordered: Boolean = true,
private val maxSaveCount: Int = 4096,
private val maxDelayTimeMS: Long = 100,
) : MongoOperator<T> by mongoOperator {
companion object {
private val unacknowledged = InsertOneResult.unacknowledged()
}
private val documentChannel: Channel<Pair<Channel<InsertOneResult?>, Document>> = Channel(1024 * 256)
private val wroteAtomic = AtomicInteger(0)
private val received = AtomicInteger(0)
val wrote get() = wroteAtomic.get()
private val job = GlobalScope.launch {
repeat(32) {
launch {
var list = ArrayList<Pair<Channel<InsertOneResult?>, Document>>()
var lastSave = System.currentTimeMillis()
suspend fun save() {
var insertManyResult = saveDocument(
list.map(Pair<Channel<InsertOneResult?>, Document>::second),
InsertManyOptions().ordered(ordered)
)
var retryTimes = 0
while (insertManyResult?.wasAcknowledged() != true && retryTimes < 3) {
retryTimes++
delay(100)
insertManyResult = saveDocument(
list.map(Pair<Channel<InsertOneResult?>, Document>::second),
InsertManyOptions().ordered(ordered)
)
}
if (insertManyResult?.wasAcknowledged() == true) {
wroteAtomic.addAndGet(insertManyResult.insertedIds.size)
list.forEachIndexed { index, (channel) ->
channel.send(InsertOneResult.acknowledged(insertManyResult.insertedIds[index]))
}
} else {
list.forEach { (channel) ->
channel.send(unacknowledged)
}
}
list = ArrayList()
lastSave = System.currentTimeMillis()
}
@Suppress("EXPERIMENTAL_API_USAGE")
while (!documentChannel.isClosedForReceive) {
try {
list.add(documentChannel.poll() ?: withTimeout(maxDelayTimeMS) { documentChannel.receive() })
received.incrementAndGet()
} catch (e: TimeoutCancellationException) {
} catch (e: ClosedReceiveChannelException) {
//println("closed")
break
}
if (list.size >= maxSaveCount || System.currentTimeMillis() - lastSave > maxDelayTimeMS) {
save()
}
}
if (list.size > 0) {
save()
}
}
}
}
override suspend fun save(entity: T, options: InsertOneOptions): Boolean {
return insertOne(entity, options)?.wasAcknowledged() ?: false
}
override suspend fun saveDocument(document: Document, options: InsertOneOptions): InsertOneResult? {
val channel = Channel<InsertOneResult?>()
documentChannel.send(channel to document)
return channel.receive()
}
override suspend fun insertOne(entity: T, options: InsertOneOptions): InsertOneResult? {
val channel = Channel<InsertOneResult?>()
documentChannel.send(channel to convertToBson(entity))
return channel.receive()
}
override fun close() {
documentChannel.close()
mongoOperator.close()
}
suspend fun join() {
job.join()
}
}

View File

@ -0,0 +1,45 @@
package cn.tursom.database.mongodb.operator
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
@Suppress("FunctionName", "unused")
fun <T : Any> DynamicCachedInsertMongoOperator(
mongoOperator: MongoOperator<T>,
executorService: ScheduledExecutorService,
loadCheckDelay: Long = 100,
loadCheckDelayTimeUnit: TimeUnit = TimeUnit.MILLISECONDS,
ordered: Boolean = true,
maxSaveCount: Int = 4096,
maxDelayTimeMS: Long = 100,
mainTransLoad: Int = 10,
spareTransLoad: Int = 50,
transWaitCount: Int = 10,
) = DynamicInsertMongoOperator(
mongoOperator,
CachedInsertMongoOperator(mongoOperator, ordered, maxSaveCount, maxDelayTimeMS),
executorService,
loadCheckDelay,
loadCheckDelayTimeUnit,
object : (DynamicInsertMongoOperator.WorkType, Int) -> DynamicInsertMongoOperator.WorkType {
private var workLoop = AtomicInteger(0)
override fun invoke(workType: DynamicInsertMongoOperator.WorkType, load: Int): DynamicInsertMongoOperator.WorkType {
when (workType) {
DynamicInsertMongoOperator.WorkType.MAIN -> if (load > spareTransLoad) {
if (workLoop.incrementAndGet() > transWaitCount) {
workLoop.set(0)
return DynamicInsertMongoOperator.WorkType.SPARE
}
}
DynamicInsertMongoOperator.WorkType.SPARE -> if (load < mainTransLoad) {
if (workLoop.incrementAndGet() > transWaitCount) {
workLoop.set(0)
return DynamicInsertMongoOperator.WorkType.MAIN
}
}
}
return workType
}
}
)

View File

@ -0,0 +1,62 @@
package cn.tursom.database.mongodb.operator
import com.mongodb.client.model.InsertOneOptions
import com.mongodb.client.result.InsertOneResult
import org.bson.Document
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
class DynamicInsertMongoOperator<T : Any>(
private val mainMongoOperator: MongoOperator<T>,
private val spareMongoOperator: MongoOperator<T>,
private val executorService: ScheduledExecutorService,
loadCheckDelay: Long = 100,
loadCheckDelayTimeUnit: TimeUnit = TimeUnit.MILLISECONDS,
private val workTypeCheck: (workType: WorkType, load: Int) -> WorkType,
) : MongoOperator<T> by mainMongoOperator {
enum class WorkType {
MAIN, SPARE
}
var workType = WorkType.MAIN
private val workLoad = AtomicInteger(0)
init {
executorService.scheduleWithFixedDelay({
workType = workTypeCheck(workType, workLoad.getAndSet(0))
}, loadCheckDelay, loadCheckDelay, loadCheckDelayTimeUnit)
}
private val workMongoOperator
get() = when (workType) {
WorkType.MAIN -> mainMongoOperator
WorkType.SPARE -> spareMongoOperator
}
override suspend fun save(entity: T, options: InsertOneOptions): Boolean {
workLoad.incrementAndGet()
return workMongoOperator.save(entity, options)
}
override suspend fun saveDocument(document: Document, options: InsertOneOptions): InsertOneResult? {
workLoad.incrementAndGet()
return workMongoOperator.saveDocument(document, options)
}
override suspend fun insertOne(entity: T, options: InsertOneOptions): InsertOneResult? {
workLoad.incrementAndGet()
return workMongoOperator.insertOne(entity, options)
}
override fun close() {
try {
mainMongoOperator.close()
} catch (e: Exception) {
}
try {
spareMongoOperator.close()
} catch (e: Exception) {
}
}
}

View File

@ -0,0 +1,115 @@
package cn.tursom.database.mongodb.operator
import cn.tursom.core.datastruct.AsyncIterator
import cn.tursom.database.mongodb.BsonFactory
import cn.tursom.database.mongodb.IndexBuilder
import cn.tursom.database.mongodb.Update
import cn.tursom.database.mongodb.Where
import com.mongodb.client.model.*
import com.mongodb.client.result.InsertManyResult
import com.mongodb.client.result.InsertOneResult
import com.mongodb.client.result.UpdateResult
import org.bson.Document
import org.bson.conversions.Bson
import java.io.Closeable
import kotlin.reflect.KProperty1
interface MongoOperator<T : Any> : BsonFactory<T>, Closeable {
suspend fun save(entity: T, options: InsertOneOptions = InsertOneOptions()): Boolean {
return insertOne(entity, options)?.wasAcknowledged() ?: false
}
suspend fun save(entities: Collection<T>, options: InsertManyOptions = InsertManyOptions()) {
insertMany(entities, options)
}
suspend fun saveDocument(document: Document, options: InsertOneOptions = InsertOneOptions()): InsertOneResult?
suspend fun saveDocument(
documents: List<Document>,
options: InsertManyOptions = InsertManyOptions()
): InsertManyResult?
suspend fun insertOne(entity: T, options: InsertOneOptions = InsertOneOptions()): InsertOneResult? {
return saveDocument(convertToBson(entity))
}
suspend fun insertMany(entities: Collection<T>, options: InsertManyOptions = InsertManyOptions()): InsertManyResult? {
return saveDocument(entities.map { convertToBson(it) }, options)
}
suspend fun updateMulti(update: Bson, where: Bson, options: UpdateOptions = UpdateOptions()): UpdateResult?
suspend fun update(update: Bson, where: Bson, options: UpdateOptions = UpdateOptions()): UpdateResult?
suspend fun update(entity: T, where: Bson, options: UpdateOptions = UpdateOptions()): UpdateResult? {
return update(convertToBson(entity), where, options)
}
@Suppress("SpellCheckingInspection")
suspend fun upsert(entity: T, where: Bson, options: UpdateOptions = UpdateOptions()): UpdateResult? {
return update(entity, where, options.upsert(true))
}
@Suppress("SpellCheckingInspection")
suspend fun upsert(update: Bson, where: Bson, options: UpdateOptions = UpdateOptions()): UpdateResult? {
return update(update, where, options.upsert(true))
}
@Suppress("SpellCheckingInspection")
suspend fun upsert(
update: Update.() -> Bson,
where: Where.() -> Bson,
options: UpdateOptions = UpdateOptions(),
): UpdateResult? {
return upsert(Update.update(), Where.where(), options)
}
suspend fun add(
field: KProperty1<T, Number?>,
value: Number,
where: Bson,
options: UpdateOptions = UpdateOptions(),
): UpdateResult? {
return upsert(
Update { field inc value },
where, options
)
}
suspend fun inc(field: KProperty1<T, Number?>, where: Bson): UpdateResult? {
return add(field, 1, where)
}
suspend fun getOne(where: Bson? = null): T?
suspend fun list(where: Bson? = null, skip: Int = 0, limit: Int = 0): List<T>
suspend fun listDocument(where: Bson? = null, skip: Int = 0, limit: Int = 0): List<Document>
fun get(where: Bson? = null, bufSize: Int = 32): AsyncIterator<T>
fun getDocument(where: Bson? = null, bufSize: Int = 32, skip: Int = 0, limit: Int = 0): AsyncIterator<Document>
fun aggregate(vararg pipeline: Bson, bufSize: Int = 32): AsyncIterator<T>
fun delete(entity: T, options: DeleteOptions = DeleteOptions())
suspend fun saveIfNotExists(entity: T) {
val document = convertToBson(entity)
upsert(document, document)
}
suspend fun count(): Long
suspend fun count(where: Bson): Long
suspend fun createIndexSuspend(key: Bson, indexOptions: IndexOptions = IndexOptions()): String?
suspend fun createIndexSuspend(indexBuilder: IndexBuilder.() -> Unit): String? {
val builder = IndexBuilder()
builder.indexBuilder()
return createIndexSuspend(builder.indexDocument, builder.indexOption)
}
override fun close() {}
}

View File

@ -0,0 +1,241 @@
package cn.tursom.database.mongodb.operator
import cn.tursom.core.datastruct.AsyncIterator
import cn.tursom.database.mongodb.*
import cn.tursom.database.mongodb.subscriber.*
import cn.tursom.log.Slf4j
import cn.tursom.log.impl.Slf4jImpl
import com.mongodb.client.model.*
import com.mongodb.client.result.InsertManyResult
import com.mongodb.client.result.InsertOneResult
import com.mongodb.client.result.UpdateResult
import com.mongodb.reactivestreams.client.MongoCollection
import com.mongodb.reactivestreams.client.MongoDatabase
import org.bson.Document
import org.bson.conversions.Bson
import org.reactivestreams.Publisher
import java.util.concurrent.Executor
import kotlin.coroutines.suspendCoroutine
import kotlin.reflect.KProperty1
@Suppress("MemberVisibilityCanBePrivate")
class MongoOperatorImpl<T : Any>(
@Suppress("MemberVisibilityCanBePrivate") val collection: MongoCollection<Document>,
clazz: Class<T>,
private val subscribeExecutor: Executor = MongoUtil.mongoExecutor,
) : MongoOperator<T>, MongoCollection<Document> by collection, BsonFactoryImpl<T>(clazz) {
companion object : Slf4j by Slf4jImpl()
constructor(
clazz: Class<T>,
db: MongoDatabase,
subscribeExecutor: Executor = MongoUtil.mongoExecutor,
) : this(db.getCollection(MongoUtil.collectionName(clazz)), clazz, subscribeExecutor)
override suspend fun save(entity: T, options: InsertOneOptions): Boolean {
return insertOne(entity, options)?.wasAcknowledged() ?: false
}
override suspend fun save(entities: Collection<T>, options: InsertManyOptions) {
insertMany(entities, options)
}
override suspend fun saveDocument(document: Document, options: InsertOneOptions): InsertOneResult? {
return suspendCoroutine { cont ->
insertOne(document, options).subscribe(SuspendInsertOneSubscriber(cont, subscribeExecutor = subscribeExecutor))
}
}
override suspend fun saveDocument(documents: List<Document>, options: InsertManyOptions): InsertManyResult? {
val publisher = collection.insertMany(documents, options)
return suspendCoroutine { cont ->
publisher.subscribe(
SuspendInsertOneSubscriber(
cont,
documents.size.toLong(),
subscribeExecutor = subscribeExecutor
)
)
}
}
override suspend fun insertOne(entity: T, options: InsertOneOptions): InsertOneResult? {
return saveDocument(convertToBson(entity))
}
override suspend fun insertMany(entities: Collection<T>, options: InsertManyOptions): InsertManyResult? {
return saveDocument(entities.map { convertToBson(it) }, options)
//val publisher = collection.insertMany(entities.map { convertToBson(it) }, options)
//return suspendCoroutine { cont ->
// publisher.subscribe(SuspendInsertListSubscriber(cont, entities.size.toLong(), subscribeExecutor = subscribeExecutor))
//}
}
override suspend fun updateMulti(update: Bson, where: Bson, options: UpdateOptions): UpdateResult? {
val publisher = collection.updateMany(where, update, options)
return suspendCoroutine { cont ->
publisher.subscribe(SuspendInsertOneSubscriber(cont, subscribeExecutor = subscribeExecutor))
}
}
override suspend fun update(update: Bson, where: Bson, options: UpdateOptions): UpdateResult? {
val publisher = collection.updateOne(where, update, options)
return suspendCoroutine { cont ->
publisher.subscribe(SuspendInsertOneSubscriber(cont, subscribeExecutor = subscribeExecutor))
}
}
override suspend fun update(entity: T, where: Bson, options: UpdateOptions): UpdateResult? {
return update(convertToBson(entity), where, options)
}
@Suppress("SpellCheckingInspection")
override suspend fun upsert(entity: T, where: Bson, options: UpdateOptions): UpdateResult? {
return update(entity, where, options.upsert(true))
}
@Suppress("SpellCheckingInspection")
override suspend fun upsert(update: Bson, where: Bson, options: UpdateOptions): UpdateResult? {
return update(update, where, options.upsert(true))
}
@Suppress("SpellCheckingInspection")
override suspend fun upsert(
update: Update.() -> Bson,
where: Where.() -> Bson,
options: UpdateOptions,
): UpdateResult? {
return upsert(Update.update(), Where.where(), options)
}
override suspend fun add(
field: KProperty1<T, Number?>,
value: Number,
where: Bson,
options: UpdateOptions,
): UpdateResult? {
return upsert(
Update { field inc value },
where, options
)
}
override suspend fun inc(field: KProperty1<T, Number?>, where: Bson): UpdateResult? {
return add(field, 1, where)
}
override suspend fun getOne(where: Bson?): T? {
val publisher = if (where == null) find() else find(where)
return suspendCoroutine { cont ->
publisher.subscribe(SuspendOneSubscriber(this, cont, subscribeExecutor = subscribeExecutor))
}
}
override suspend fun list(where: Bson?, skip: Int, limit: Int): List<T> {
val publisher = if (where == null) find() else find(where)
publisher.skip(skip)
if (limit > 0) {
publisher.limit(limit)
}
return suspendCoroutine { cont ->
publisher.subscribe(
SuspendListSubscriber(
this,
cont,
if (limit > 0) limit.toLong() else Long.MAX_VALUE,
subscribeExecutor = subscribeExecutor
)
)
}
}
override suspend fun listDocument(where: Bson?, skip: Int, limit: Int): List<Document> {
println("skip: $skip, limit: $limit")
val publisher = if (where == null) find() else find(where)
publisher.skip(skip)
if (limit > 0) {
publisher.limit(limit)
}
return suspendCoroutine { cont ->
publisher.subscribe(
SuspendListDocumentSubscriber(
cont,
if (limit > 0) limit.toLong() else Long.MAX_VALUE,
subscribeExecutor = subscribeExecutor
)
)
}
}
override fun get(where: Bson?, bufSize: Int): AsyncIterator<T> {
val find = if (where == null) find() else find(where)
return iterator(find, bufSize)
}
override fun getDocument(where: Bson?, bufSize: Int, skip: Int, limit: Int): AsyncIterator<Document> {
val find = if (where == null) find() else find(where)
find.skip(skip)
if (limit > 0) {
find.limit(limit)
}
return documentIterator(find, bufSize)
}
override fun aggregate(vararg pipeline: Bson, bufSize: Int): AsyncIterator<T> =
iterator(aggregate(pipeline.asList()), bufSize)
private fun iterator(publisher: Publisher<Document>, bufSize: Int = 32): AsyncIterator<T> {
val subscriber = AsyncIteratorSubscriber(
this,
bufSize,
subscribeExecutor = subscribeExecutor
)
publisher.subscribe(subscriber)
return subscriber
}
private fun documentIterator(publisher: Publisher<Document>, bufSize: Int = 32): AsyncIterator<Document> {
val subscriber = AsyncDocumentIteratorSubscriber(bufSize, subscribeExecutor = subscribeExecutor)
publisher.subscribe(subscriber)
return subscriber
}
override fun delete(entity: T, options: DeleteOptions) {
deleteOne(convertToBson(entity), options)
}
override suspend fun saveIfNotExists(entity: T) {
val document = convertToBson(entity)
upsert(document, document)
}
override suspend fun count(): Long {
val publisher = countDocuments()
return suspendCoroutine { cont ->
publisher.subscribe(SuspendInsertOneSubscriber(cont, subscribeExecutor = subscribeExecutor))
} ?: 0L
}
override suspend fun count(where: Bson): Long {
val publisher = countDocuments(where)
return suspendCoroutine { cont ->
publisher.subscribe(SuspendInsertOneSubscriber(cont, subscribeExecutor = subscribeExecutor))
} ?: 0L
}
override suspend fun createIndexSuspend(key: Bson, indexOptions: IndexOptions): String? {
return suspendCoroutine { cont ->
createIndex(key, indexOptions).subscribe(SuspendInsertOneSubscriber(cont, subscribeExecutor = subscribeExecutor))
}
}
override suspend fun createIndexSuspend(indexBuilder: IndexBuilder.() -> Unit): String? {
val builder = IndexBuilder()
builder.indexBuilder()
return createIndexSuspend(builder.indexDocument, builder.indexOption)
}
override fun toString(): String {
return "MongoOperator(collection=$collection, clazz=$clazz)"
}
}

View File

@ -0,0 +1,44 @@
package cn.tursom.database.mongodb.subscriber
import cn.tursom.database.mongodb.MongoUtil
import org.bson.Document
import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription
import java.util.concurrent.Executor
@Suppress("ReactiveStreamsSubscriberImplementation")
abstract class AbstractDocumentSubscriber(
val size: Long = 1,
val subscribeExecutor: Executor = MongoUtil.mongoExecutor,
) : Subscriber<Document> {
abstract fun next(t: Document)
private var requestRemain = 0L
protected var requested = 0
protected lateinit var s: Subscription
protected var compete = false
override fun onComplete() {
compete = true
}
override fun onSubscribe(s: Subscription) {
this.s = s
requestRemain = size - requested
subscribeExecutor.execute {
s.request(requestRemain)
}
}
override fun onNext(t: Document) {
next(t)
requestRemain--
requested++
if (requestRemain > 0) return
if (size - requested > 0) {
s.request(size - requested)
} else {
onComplete()
}
}
}

View File

@ -0,0 +1,46 @@
package cn.tursom.database.mongodb.subscriber
import cn.tursom.database.mongodb.BsonFactory
import cn.tursom.database.mongodb.MongoUtil
import org.bson.Document
import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription
import java.util.concurrent.Executor
@Suppress("ReactiveStreamsSubscriberImplementation")
abstract class AbstractSubscriber<T>(
private val bsonFactory: BsonFactory<T>,
val size: Long = 1,
val subscribeExecutor: Executor = MongoUtil.mongoExecutor,
) : Subscriber<Document> {
abstract fun next(t: T)
private var requestRemain = 0L
protected var requested = 0
protected lateinit var s: Subscription
protected var compete = false
override fun onComplete() {
compete = true
}
override fun onSubscribe(s: Subscription) {
this.s = s
requestRemain = size - requested
subscribeExecutor.execute {
s.request(requestRemain)
}
}
override fun onNext(t: Document) {
next(bsonFactory.parse(t))
requestRemain--
requested++
if (requestRemain > 0) return
if (size - requested > 0) {
s.request(size - requested)
} else {
onComplete()
}
}
}

View File

@ -0,0 +1,72 @@
package cn.tursom.database.mongodb.subscriber
import cn.tursom.core.Disposable
import cn.tursom.core.datastruct.AsyncIterator
import cn.tursom.database.mongodb.MongoUtil
import org.bson.Document
import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.Executor
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
@Suppress("ReactiveStreamsSubscriberImplementation")
class AsyncDocumentIteratorSubscriber(
val bufSize: Int = 32,
val subscribeExecutor: Executor = MongoUtil.mongoExecutor,
) : Subscriber<Document>, AsyncIterator<Document> {
protected lateinit var s: Subscription
@Volatile
protected var compete = false
private var cont = Disposable<Continuation<Document>>()
private var notify = Disposable<Continuation<Unit>>()
private val cache = ConcurrentLinkedQueue<Document>()
override fun onComplete() {
cont.get()?.resumeWithException(Exception())
notify.get()?.resume(Unit)
compete = true
}
override fun onNext(t: Document) {
cont.get()?.resume(t) ?: cache.add(t)
notify.get()?.resume(Unit)
}
override fun onSubscribe(s: Subscription) {
this.s = s
subscribeExecutor.execute {
s.request(1)
}
}
override fun onError(t: Throwable) {
cont.get()?.resumeWithException(t) ?: t.printStackTrace()
}
override suspend fun next(): Document {
return cache.poll() ?: suspendCoroutine { cont ->
this.cont.set(cont)
s.request(bufSize.toLong())
cache.poll()?.let { this.cont.get()?.resume(it) ?: cache.add(it) }
}
}
override suspend fun hasNext(): Boolean {
if (cache.isEmpty() && !compete) {
suspendCoroutine<Unit> {
notify.set(it)
s.request(bufSize.toLong())
if (cache.isNotEmpty()) {
notify.get()?.resume(Unit)
}
}
}
return cache.isNotEmpty()
}
}

View File

@ -0,0 +1,78 @@
package cn.tursom.database.mongodb.subscriber
import cn.tursom.core.Disposable
import cn.tursom.core.datastruct.AsyncIterator
import cn.tursom.database.mongodb.BsonFactory
import cn.tursom.database.mongodb.MongoUtil
import org.bson.Document
import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.Executor
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
@Suppress("ReactiveStreamsSubscriberImplementation")
class AsyncIteratorSubscriber<T>(
private val bsonFactory: BsonFactory<T>,
val bufSize: Int = 32,
val subscribeExecutor: Executor = MongoUtil.mongoExecutor,
) : Subscriber<Document>, AsyncIterator<T>, BsonFactory<T> by bsonFactory {
protected lateinit var s: Subscription
@Volatile
protected var compete = false
private var cont = Disposable<Continuation<T>>()
private var notify = Disposable<Continuation<Unit>>()
private val cache = ConcurrentLinkedQueue<T>()
override fun onComplete() {
cont.get()?.resumeWithException(Exception())
notify.get()?.resume(Unit)
compete = true
}
override fun onNext(t: Document) {
next(bsonFactory.parse(t))
}
override fun onSubscribe(s: Subscription) {
this.s = s
subscribeExecutor.execute {
s.request(1)
}
}
fun next(t: T) {
cont.get()?.resume(t) ?: cache.add(t)
notify.get()?.resume(Unit)
}
override fun onError(t: Throwable) {
cont.get()?.resumeWithException(t) ?: t.printStackTrace()
}
override suspend fun next(): T {
return cache.poll() ?: suspendCoroutine { cont ->
this.cont.set(cont)
s.request(bufSize.toLong())
cache.poll()?.let { this.cont.get()?.resume(it) ?: cache.add(it) }
}
}
override suspend fun hasNext(): Boolean {
if (cache.isEmpty() && !compete) {
suspendCoroutine<Unit> {
notify.set(it)
s.request(bufSize.toLong())
if (cache.isNotEmpty()) {
notify.get()?.resume(Unit)
}
}
}
return cache.isNotEmpty()
}
}

View File

@ -0,0 +1,34 @@
package cn.tursom.database.mongodb.subscriber
import cn.tursom.database.mongodb.MongoUtil
import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription
import java.util.concurrent.Executor
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
@Suppress("ReactiveStreamsSubscriberImplementation")
class SuspendInsertListSubscriber<T>(
val cont: Continuation<List<T>>,
val requestSize: Long = Long.MAX_VALUE,
val subscribeExecutor: Executor = MongoUtil.mongoExecutor,
) : Subscriber<T> {
val resultList = ArrayList<T>()
override fun onComplete() {
cont.resume(resultList)
}
override fun onSubscribe(s: Subscription) {
if (requestSize > 0) subscribeExecutor.execute { s.request(requestSize) }
else onComplete()
}
override fun onNext(t: T) {
resultList.add(t)
}
override fun onError(t: Throwable) {
cont.resumeWithException(t)
}
}

View File

@ -0,0 +1,34 @@
package cn.tursom.database.mongodb.subscriber
import cn.tursom.core.Disposable
import cn.tursom.database.mongodb.MongoUtil
import org.reactivestreams.Subscriber
import org.reactivestreams.Subscription
import java.util.concurrent.Executor
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
@Suppress("ReactiveStreamsSubscriberImplementation")
class SuspendInsertOneSubscriber<T>(
cont: Continuation<T?>,
val requestSize: Long = 1,
val subscribeExecutor: Executor = MongoUtil.mongoExecutor,
) : Subscriber<T> {
val contDisposable = Disposable(cont)
override fun onComplete() {
contDisposable.get()?.resume(null)
}
override fun onSubscribe(s: Subscription) = if (requestSize > 0) {
subscribeExecutor.execute { s.request(requestSize) }
} else onComplete()
override fun onNext(t: T) {
contDisposable.get()?.resume(t)
}
override fun onError(t: Throwable) {
contDisposable.get()?.resumeWithException(t)
}
}

View File

@ -0,0 +1,27 @@
package cn.tursom.database.mongodb.subscriber
import cn.tursom.database.mongodb.MongoUtil
import org.bson.Document
import java.util.concurrent.Executor
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
@Suppress("ReactiveStreamsSubscriberImplementation")
class SuspendListDocumentSubscriber(
val cont: Continuation<List<Document>>,
size: Long = Long.MAX_VALUE,
subscribeExecutor: Executor = MongoUtil.mongoExecutor,
) : AbstractDocumentSubscriber(size, subscribeExecutor) {
companion object {
const val maxDefaultArrayCache = 4096
}
val resultList = ArrayList<Document>(if (size < maxDefaultArrayCache) size.toInt() else maxDefaultArrayCache)
override fun onComplete() = cont.resume(resultList)
override fun onError(t: Throwable) = cont.resumeWithException(t)
override fun next(t: Document) {
resultList.add(t)
}
}

View File

@ -0,0 +1,28 @@
package cn.tursom.database.mongodb.subscriber
import cn.tursom.database.mongodb.BsonFactory
import cn.tursom.database.mongodb.MongoUtil
import java.util.concurrent.Executor
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
@Suppress("ReactiveStreamsSubscriberImplementation")
class SuspendListSubscriber<T>(
bsonFactory: BsonFactory<T>,
val cont: Continuation<List<T>>,
size: Long = Long.MAX_VALUE,
subscribeExecutor: Executor = MongoUtil.mongoExecutor,
) : AbstractSubscriber<T>(bsonFactory, size, subscribeExecutor) {
companion object {
const val maxDefaultArrayCache = 4096
}
val resultList = ArrayList<T>(if (size < maxDefaultArrayCache) size.toInt() else maxDefaultArrayCache)
override fun onComplete() = cont.resume(resultList)
override fun onError(t: Throwable) = cont.resumeWithException(t)
override fun next(t: T) {
resultList.add(t)
}
}

View File

@ -0,0 +1,30 @@
package cn.tursom.database.mongodb.subscriber
import cn.tursom.core.Disposable
import cn.tursom.database.mongodb.BsonFactory
import cn.tursom.database.mongodb.MongoUtil
import java.util.concurrent.Executor
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
@Suppress("ReactiveStreamsSubscriberImplementation")
class SuspendOneSubscriber<T>(
bsonFactory: BsonFactory<T>,
cont: Continuation<T?>,
size: Long = 1,
subscribeExecutor: Executor = MongoUtil.mongoExecutor,
) : AbstractSubscriber<T>(bsonFactory, size, subscribeExecutor) {
val contDisposable = Disposable(cont)
override fun onComplete() {
contDisposable.get()?.resume(null)
}
override fun next(t: T) {
contDisposable.get()?.resume(t)
}
override fun onError(t: Throwable) {
contDisposable.get()?.resumeWithException(t)
}
}

View File

@ -0,0 +1,5 @@
package cn.tursom.database.mongodb
import com.mongodb.reactivestreams.client.MongoClient
fun MongoClient.getMongoTemplate(db: String) = MongoTemplate(this, db)

View File

@ -0,0 +1,33 @@
plugins {
kotlin("jvm")
`maven-publish`
}
dependencies {
api(project(":"))
implementation(project(":ts-core"))
compileOnly(group = "org.springframework.data", name = "spring-data-mongodb", version = "3.1.7")
}
@kotlin.Suppress("UNCHECKED_CAST")
(rootProject.ext["excludeTest"] as (Project, TaskContainer) -> Unit)(project, tasks)
tasks.register("install") {
finalizedBy(tasks["publishToMavenLocal"])
}
publishing {
publications {
create<MavenPublication>("maven") {
groupId = project.group.toString()
artifactId = project.name
version = project.version.toString()
from(components["java"])
try {
artifact(tasks["sourcesJar"])
} catch (e: Exception) {
}
}
}
}

View File

@ -0,0 +1,95 @@
package cn.tursom.database.mongodb.spring
import org.springframework.data.mongodb.core.aggregation.Aggregation
import org.springframework.data.mongodb.core.aggregation.AggregationOperation
import kotlin.reflect.KProperty
@Suppress("unused")
object AggregationBuilder : MongoName {
inline infix operator fun invoke(
operatorBuilder: AggregationBuilder.(MutableList<AggregationOperation>) -> MutableList<AggregationOperation>
): Aggregation {
val operator = this.operatorBuilder(ArrayList())
return Aggregation.newAggregation(operator)
}
infix operator fun MutableList<AggregationOperation>.plus(
operation: AggregationOperation
): MutableList<AggregationOperation> {
add(operation)
return this
}
infix fun MutableList<AggregationOperation>.match(
builder: QueryBuilder.() -> Unit
): MutableList<AggregationOperation> {
add(Aggregation.match(QueryBuilder criteria builder))
return this
}
infix fun MutableList<AggregationOperation>.group(
builder: GroupBuilder.() -> AggregationOperation
): MutableList<AggregationOperation> {
add(GroupBuilder.builder())
return this
}
infix fun MutableList<AggregationOperation>.project(
builder: ProjectBuilder.() -> AggregationOperation
): MutableList<AggregationOperation> {
add(ProjectBuilder.builder())
return this
}
infix fun MutableList<AggregationOperation>.unwind(
field: String
): MutableList<AggregationOperation> {
add(Aggregation.unwind(field))
return this
}
infix fun MutableList<AggregationOperation>.unwind(
field: KProperty<*>
): MutableList<AggregationOperation> {
add(Aggregation.unwind(field.mongoName))
return this
}
@JvmName("unwindString")
infix fun MutableList<AggregationOperation>.unwind(
field: () -> String
): MutableList<AggregationOperation> {
add(Aggregation.unwind(field()))
return this
}
infix fun MutableList<AggregationOperation>.unwind(
field: () -> KProperty<*>
): MutableList<AggregationOperation> {
add(Aggregation.unwind(field().mongoName))
return this
}
infix fun MutableList<AggregationOperation>.sort(
builder: SortBuilder.() -> Unit
): MutableList<AggregationOperation> {
val sortBuilder = SortBuilder()
sortBuilder.invoke(builder)
add(sortBuilder.sortOperation)
return this
}
infix fun MutableList<AggregationOperation>.skip(
skip: Long
): MutableList<AggregationOperation> {
add(Aggregation.skip(skip))
return this
}
infix fun MutableList<AggregationOperation>.limit(
limit: Long
): MutableList<AggregationOperation> {
add(Aggregation.limit(limit))
return this
}
}

View File

@ -0,0 +1,33 @@
package cn.tursom.database.mongodb.spring
import org.bson.types.Decimal128
import java.math.BigDecimal
/**
* 用来将Object类型转换为Document类型的转换器
* 设计成接口与单例对象共存的方式
* 既可以用 BsonConverter.bsonValue
* 也可以让类继承于 BsonConverter 以直接调用 Any.bsonValue 方法
*/
interface BsonConverter {
companion object {
fun bsonValue(obj: Any): Any = when (obj) {
is BigDecimal -> Decimal128(obj)
is String, is Number, is Boolean -> obj
is Map<*, *> -> MongoUtil.convertToBson(obj)
is Iterable<*> -> obj.mapNotNull { bsonValue(it ?: return@mapNotNull null) }
is Array<*> -> obj.mapNotNull { bsonValue(it ?: return@mapNotNull null) }
is ByteArray -> obj
is ShortArray -> obj.asList()
is IntArray -> obj.asList()
is LongArray -> obj.asList()
is FloatArray -> obj.asList()
is DoubleArray -> obj.asList()
is BooleanArray -> obj.asList()
is CharArray -> obj.asList()
else -> MongoUtil.convertToBson(obj)
}
}
fun Any.bsonValue(): Any = bsonValue(this)
}

View File

@ -0,0 +1,24 @@
package cn.tursom.database.mongodb.spring
import cn.tursom.core.Parser
import org.bson.Document
interface BsonFactory<T> {
val clazz: Class<T>
fun parse(document: Document) = Parser.parse(document, clazz)!!
fun convertToBson(entity: T): Document
//fun convertToEntity(bson: Document): T
companion object {
operator fun <T> get(clazz: Class<T>): BsonFactory<T> {
return BsonFactoryImpl(clazz)
}
inline fun <reified T : Any?> get(): BsonFactory<T> {
return get(T::class.java)
}
}
}

View File

@ -0,0 +1,24 @@
package cn.tursom.database.mongodb.spring
import cn.tursom.core.isStatic
import cn.tursom.core.isTransient
import org.bson.Document
open class BsonFactoryImpl<T>(final override val clazz: Class<T>) : BsonFactory<T> {
private val fields = clazz.declaredFields.filter {
it.isAccessible = true
!it.isStatic() && !it.isTransient()
}
override fun convertToBson(entity: T): Document {
val bson = Document()
fields.forEach {
MongoUtil.injectValue(bson, it.get(entity) ?: return@forEach, it)
}
return bson
}
override fun toString(): String {
return "BsonFactoryImpl(clazz=$clazz, fields=$fields)"
}
}

View File

@ -0,0 +1,155 @@
package cn.tursom.database.mongodb.spring
import org.bson.BsonRegularExpression
import org.bson.Document
import org.bson.types.ObjectId
import org.springframework.data.domain.Example
import org.springframework.data.geo.Circle
import org.springframework.data.geo.Point
import org.springframework.data.geo.Shape
import org.springframework.data.mongodb.core.geo.GeoJson
import org.springframework.data.mongodb.core.query.Criteria
import org.springframework.data.mongodb.core.query.Query
import org.springframework.data.mongodb.core.schema.MongoJsonSchema
import java.util.regex.Pattern
import kotlin.reflect.KProperty
@Suppress("MemberVisibilityCanBePrivate", "unused", "HasPlatformType")
object CriteriaBuilder : MongoName, BsonConverter {
const val where = "\$where"
fun String.asJs() = where equal this
fun String.asObjectId() = ObjectId(this)
infix fun where(where: String) = Criteria.where(where)
infix fun where(where: KProperty<*>) = Criteria.where(where.mongoName)
fun String.asWhere() = Criteria.where(this)
fun KProperty<*>.asWhere() = mongoName.asWhere()
infix fun Criteria.and(where: Criteria) = Criteria().andOperator(this, where)
infix operator fun Criteria.plus(where: Criteria) = and(where)
private val orOperator: Criteria.(Array<out Criteria>) -> Criteria = Criteria::orOperator
private val andOperator: Criteria.(Array<out Criteria>) -> Criteria = Criteria::andOperator
fun or(vararg where: Criteria) = Criteria().orOperator(where)
val or: (Array<out Criteria>) -> Criteria = CriteriaBuilder::or
fun and(vararg where: Criteria) = Criteria().andOperator(where)
val and: (Array<out Criteria>) -> Criteria = CriteriaBuilder::and
infix fun Criteria.or(where: Criteria) = or(this, where)
infix fun Criteria.gt(where: Any) = gt(where.bsonValue()) // >
infix fun Criteria.gte(where: Any) = gte(where.bsonValue()) // >=
infix fun Criteria.`in`(where: Collection<Any?>) = `in`(where.mapNotNull { it?.bsonValue() }) // contains
infix fun Criteria.`is`(where: Any?) = `is`(where?.bsonValue()) // ==
infix fun Criteria.ne(where: Any?) = ne(where?.bsonValue()) // !=
infix fun Criteria.lt(where: Any) = lt(where.bsonValue()) // <
infix fun Criteria.lte(where: Any) = lte(where.bsonValue()) // <=
infix fun Criteria.nin(where: Collection<Any?>) = nin(where.bsonValue()) // not contains
infix fun Criteria.all(where: Any?) = all(where?.bsonValue())
infix fun Criteria.all(where: Collection<Any?>) = all(where.bsonValue())
infix fun Criteria.size(s: Int) = size(s)
infix fun Criteria.exists(b: Boolean) = exists(b)
infix fun Criteria.type(t: Int) = type(t)
operator fun Criteria.not() = not()
infix fun Criteria.regex(re: String) = regex(re)
infix fun Criteria.regex(pattern: Pattern) = regex(pattern)
infix fun Criteria.regex(pattern: Regex) = regex(pattern.toPattern())
infix fun Criteria.regex(regex: BsonRegularExpression) = regex(regex)
infix fun Criteria.withinSphere(circle: Circle) = withinSphere(circle)
infix fun Criteria.within(shape: Shape) = within(shape)
infix fun Criteria.near(point: Point) = near(point)
infix fun Criteria.nearSphere(point: Point) = nearSphere(point)
infix fun Criteria.intersects(point: GeoJson<*>) = intersects(point)
infix fun Criteria.maxDistance(maxDistance: Double) = maxDistance(maxDistance)
infix fun Criteria.minDistance(maxDistance: Double) = minDistance(maxDistance)
infix fun Criteria.elemMatch(c: Criteria) = elemMatch(c)
infix fun Criteria.alike(sample: Example<*>) = alike(sample)
infix fun Criteria.andDocumentStructureMatches(schema: MongoJsonSchema) = andDocumentStructureMatches(schema)
infix fun Criteria.eq(where: Any?) = `is`(where?.bsonValue())
infix fun Criteria.equal(where: Any?) = `is`(where?.bsonValue())
infix fun KProperty<*>.gt(where: Any) = asWhere() gt where // >
infix fun KProperty<*>.gte(where: Any) = asWhere() gte where // >=
fun KProperty<*>.`in`(vararg where: Any?) = asWhere() `in` where.asList() // contains
infix fun KProperty<*>.`in`(where: Collection<Any?>) = asWhere() `in` where // contains
infix fun KProperty<*>.`is`(where: Any?) = asWhere() `is` where // ==
infix fun KProperty<*>.ne(where: Any?) = asWhere() ne where // !=
infix fun KProperty<*>.lt(where: Any) = asWhere() lt where // <
infix fun KProperty<*>.lte(where: Any) = asWhere() lte where // <=
fun KProperty<*>.nin(vararg where: Any?) = asWhere() nin where.asList() // not contains
infix fun KProperty<*>.nin(where: Collection<Any?>) = asWhere() nin where // not contains
infix fun KProperty<*>.all(where: Any?) = asWhere() all where
infix fun KProperty<*>.all(where: Collection<Any?>) = asWhere() all where
infix fun KProperty<*>.size(s: Int) = asWhere() size s
infix fun KProperty<*>.exists(b: Boolean) = asWhere() exists b
infix fun KProperty<*>.type(t: Int) = asWhere() type t
operator fun KProperty<*>.not() = asWhere().not()
infix fun KProperty<*>.regex(re: String) = asWhere() regex re
infix fun KProperty<*>.regex(pattern: Pattern) = asWhere() regex pattern
infix fun KProperty<*>.regex(pattern: Regex) = asWhere() regex pattern.toPattern()
infix fun KProperty<*>.regex(regex: BsonRegularExpression) = asWhere() regex regex
infix fun KProperty<*>.withinSphere(circle: Circle) = asWhere() withinSphere circle
infix fun KProperty<*>.within(shape: Shape) = asWhere() within shape
infix fun KProperty<*>.near(point: Point) = asWhere() near point
infix fun KProperty<*>.nearSphere(point: Point) = asWhere() nearSphere point
infix fun KProperty<*>.intersects(point: GeoJson<*>) = asWhere() intersects point
infix fun KProperty<*>.maxDistance(maxDistance: Double) = asWhere() maxDistance maxDistance
infix fun KProperty<*>.minDistance(maxDistance: Double) = asWhere() minDistance maxDistance
infix fun KProperty<*>.elemMatch(c: Criteria) = asWhere() elemMatch c
infix fun KProperty<*>.alike(sample: Example<*>) = asWhere() alike sample
infix fun KProperty<*>.andDocumentStructureMatches(schema: MongoJsonSchema) =
asWhere() andDocumentStructureMatches schema
infix fun KProperty<*>.eq(where: Any?) = asWhere() equal where
infix fun KProperty<*>.equal(where: Any?) = asWhere() equal where
infix fun String.gt(where: Any) = asWhere() gt where // >
infix fun String.gte(where: Any) = asWhere() gte where // >=
fun String.`in`(vararg where: Any?) = asWhere() `in` where.asList() // contains
infix fun String.`in`(where: Collection<Any?>) = asWhere() `in` where // contains
infix fun String.`is`(where: Any?) = asWhere() `is` where // ==
infix fun String.ne(where: Any?) = asWhere() ne where // !=
infix fun String.lt(where: Any) = asWhere() lt where // <
infix fun String.lte(where: Any) = asWhere() lte where // <=
fun String.nin(vararg where: Any?) = asWhere() nin where.asList() // not contains
infix fun String.nin(where: Collection<Any?>) = asWhere() nin where // not contains
infix fun String.all(where: Any?) = asWhere() all where
infix fun String.all(where: Collection<Any?>) = asWhere() all where
infix fun String.size(s: Int) = asWhere() size s
infix fun String.exists(b: Boolean) = asWhere() exists b
infix fun String.type(t: Int) = asWhere() type t
operator fun String.not() = asWhere().not()
infix fun String.regex(re: String) = asWhere() regex re
infix fun String.regex(pattern: Pattern) = asWhere() regex pattern
infix fun String.regex(pattern: Regex) = asWhere() regex pattern.toPattern()
infix fun String.regex(regex: BsonRegularExpression) = asWhere() regex regex
infix fun String.withinSphere(circle: Circle) = asWhere() withinSphere circle
infix fun String.within(shape: Shape) = asWhere() within shape
infix fun String.near(point: Point) = asWhere() near point
infix fun String.nearSphere(point: Point) = asWhere() nearSphere point
infix fun String.intersects(point: GeoJson<*>) = asWhere() intersects point
infix fun String.maxDistance(maxDistance: Double) = asWhere() maxDistance maxDistance
infix fun String.minDistance(maxDistance: Double) = asWhere() minDistance maxDistance
infix fun String.elemMatch(c: Criteria) = asWhere() elemMatch c
infix fun String.alike(sample: Example<*>) = asWhere() alike sample
infix fun String.andDocumentStructureMatches(schema: MongoJsonSchema) = asWhere() andDocumentStructureMatches schema
infix fun String.eq(where: Any?) = asWhere() equal where
infix fun String.equal(where: Any?) = asWhere() equal where
inline infix operator fun invoke(operator: CriteriaBuilder.() -> Criteria): Criteria = this.operator()
inline infix fun query(operator: CriteriaBuilder.() -> Criteria): Query = Query(this.operator())
inline infix fun queryObject(operator: CriteriaBuilder.() -> Criteria): Document =
Query(this.operator()).queryObject
inline infix fun fieldsObject(operator: CriteriaBuilder.() -> Criteria): Document =
Query(this.operator()).fieldsObject
inline infix fun sortObject(operator: CriteriaBuilder.() -> Criteria): Document = Query(this.operator()).sortObject
}

View File

@ -0,0 +1,8 @@
package cn.tursom.database.mongodb.spring
import org.springframework.data.mongodb.core.query.Criteria
import org.springframework.data.mongodb.core.query.Query
object CriteriaQuery : MongoName, BsonConverter {
inline infix operator fun invoke(operator: CriteriaBuilder.() -> Criteria): Query = Query(CriteriaBuilder.operator())
}

View File

@ -0,0 +1,57 @@
package cn.tursom.database.mongodb.spring
import org.springframework.data.mongodb.core.aggregation.Aggregation
import org.springframework.data.mongodb.core.aggregation.GroupOperation
import kotlin.reflect.KProperty
@Suppress("unused")
object GroupBuilder : MongoName, BsonConverter {
inline infix operator fun invoke(operator: GroupBuilder.() -> GroupOperation): GroupOperation = this.operator()
fun group(vararg fields: String): GroupOperation = Aggregation.group(*fields)
fun group(vararg fields: KProperty<*>): GroupOperation =
Aggregation.group(*fields.map { it.mongoName }.toTypedArray())
infix fun GroupOperation.sum(reference: String): GroupOperation.GroupOperationBuilder = sum(reference)
infix fun GroupOperation.sum(reference: KProperty<*>): GroupOperation.GroupOperationBuilder = sum(reference.mongoName)
infix fun GroupOperation.GroupOperationBuilder.`as`(alias: String): GroupOperation = `as`(alias)
infix fun GroupOperation.GroupOperationBuilder.`as`(alias: KProperty<*>): GroupOperation = `as`(alias.mongoName)
infix fun GroupOperation.addToSet(reference: String): GroupOperation.GroupOperationBuilder = addToSet(reference)
infix fun GroupOperation.addToSet(reference: KProperty<*>): GroupOperation.GroupOperationBuilder =
addToSet(reference.mongoName)
infix fun GroupOperation.addToSet(value: Any): GroupOperation.GroupOperationBuilder = addToSet(value)
infix fun GroupOperation.last(reference: String): GroupOperation.GroupOperationBuilder = last(reference)
infix fun GroupOperation.last(reference: KProperty<*>): GroupOperation.GroupOperationBuilder =
last(reference.mongoName)
infix fun GroupOperation.first(reference: String): GroupOperation.GroupOperationBuilder = first(reference)
infix fun GroupOperation.first(reference: KProperty<*>): GroupOperation.GroupOperationBuilder =
first(reference.mongoName)
infix fun GroupOperation.avg(reference: String): GroupOperation.GroupOperationBuilder = avg(reference)
infix fun GroupOperation.avg(reference: KProperty<*>): GroupOperation.GroupOperationBuilder = avg(reference.mongoName)
infix fun GroupOperation.push(reference: String): GroupOperation.GroupOperationBuilder = push(reference)
infix fun GroupOperation.push(reference: KProperty<*>): GroupOperation.GroupOperationBuilder =
push(reference.mongoName)
infix fun GroupOperation.push(value: Any): GroupOperation.GroupOperationBuilder = push(value)
infix fun GroupOperation.min(reference: String): GroupOperation.GroupOperationBuilder = min(reference)
infix fun GroupOperation.min(reference: KProperty<*>): GroupOperation.GroupOperationBuilder = min(reference.mongoName)
infix fun GroupOperation.max(reference: String): GroupOperation.GroupOperationBuilder = max(reference)
infix fun GroupOperation.max(reference: KProperty<*>): GroupOperation.GroupOperationBuilder = max(reference.mongoName)
infix fun GroupOperation.stdDevSamp(reference: String): GroupOperation.GroupOperationBuilder = stdDevSamp(reference)
infix fun GroupOperation.stdDevSamp(reference: KProperty<*>): GroupOperation.GroupOperationBuilder =
stdDevSamp(reference.mongoName)
infix fun GroupOperation.stdDevPop(reference: String): GroupOperation.GroupOperationBuilder = stdDevPop(reference)
infix fun GroupOperation.stdDevPop(reference: KProperty<*>): GroupOperation.GroupOperationBuilder =
stdDevPop(reference.mongoName)
}

View File

@ -0,0 +1,87 @@
package cn.tursom.database.mongodb.spring
import cn.tursom.database.mongodb.spring.function.Function1
import cn.tursom.database.mongodb.spring.function.field
import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.mapping.Field
import kotlin.reflect.KProperty
import kotlin.reflect.jvm.javaField
interface MongoName {
companion object {
const val dollar = '$'
inline operator fun <T> invoke(builder: Companion.() -> T) = this.builder()
inline val KProperty<*>.mongoName: String
get() = mongoName(this)
fun arrayElement(elementName: String = "") = if (elementName.isBlank()) {
"$"
} else {
"$[$elementName]"
}
fun mongoName(kProperty: KProperty<*>): String {
if (kProperty.javaField?.getAnnotation(Id::class.java) != null) {
return "_id"
}
val field = kProperty.javaField?.getAnnotation(Field::class.java)
if (field != null) {
if (field.value.isNotEmpty()) {
return field.value
}
}
return kProperty.name
}
fun mongoName(field: java.lang.reflect.Field): String {
if (field.getAnnotation(Id::class.java) != null) {
return "_id"
}
val fieldAnnotation = field.getAnnotation(Field::class.java)
if (fieldAnnotation != null) {
if (fieldAnnotation.value.isNotEmpty()) {
return fieldAnnotation.value
}
}
return field.name
}
fun <T> mongoName(getter: Function1<*, T>): String = mongoName(getter.field!!)
infix operator fun KProperty<*>.plus(kProperty: KProperty<*>) = "$mongoName.${kProperty.mongoName}"
infix operator fun KProperty<*>.plus(field: String) = "$mongoName.${field}"
infix operator fun String.plus(kProperty: KProperty<*>) = "$this.${kProperty.mongoName}"
infix operator fun String.plus(field: String) = "$this.${field}"
infix operator fun KProperty<*>.get(kProperty: KProperty<*>) = plus(kProperty)
infix operator fun KProperty<*>.get(field: String) = plus(field)
infix operator fun String.get(kProperty: KProperty<*>) = plus(kProperty)
infix operator fun String.get(field: String) = plus(field)
infix operator fun KProperty<*>.rem(kProperty: KProperty<*>) = plus(kProperty)
infix operator fun KProperty<*>.rem(field: String) = plus(field)
infix operator fun String.rem(kProperty: KProperty<*>) = plus(kProperty)
infix operator fun String.rem(field: String) = plus(field)
}
val dollar get() = Companion.dollar
val KProperty<*>.mongoName: String
get() = mongoName(this)
fun arrayElement(elementName: String = "") = Companion.arrayElement(elementName)
fun mongoName(kProperty: KProperty<*>): String = Companion.mongoName(kProperty)
fun mongoName(field: java.lang.reflect.Field): String = Companion.mongoName(field)
fun <T> mongoName(getter: Function1<*, T>): String = Companion.mongoName(getter)
infix operator fun KProperty<*>.get(kProperty: KProperty<*>) = Companion { this@get plus kProperty }
infix operator fun KProperty<*>.get(field: String) = Companion { this@get plus field }
infix operator fun String.get(kProperty: KProperty<*>) = Companion { this@get plus kProperty }
infix operator fun String.get(field: String) = Companion { this@get plus field }
infix operator fun KProperty<*>.rem(kProperty: KProperty<*>) = Companion { this@rem plus kProperty }
infix operator fun KProperty<*>.rem(field: String) = Companion { this@rem plus field }
infix operator fun String.rem(kProperty: KProperty<*>) = Companion { this@rem plus kProperty }
infix operator fun String.rem(field: String) = Companion { this@rem plus field }
}

View File

@ -0,0 +1,82 @@
package cn.tursom.database.mongodb.spring
import cn.tursom.core.isStatic
import cn.tursom.core.isTransient
import cn.tursom.core.uncheckedCast
import org.bson.BsonValue
import org.bson.Document
import org.bson.conversions.Bson
import java.lang.reflect.Field
object MongoUtil {
fun collectionName(clazz: Class<*>) =
clazz.getAnnotation(org.springframework.data.mongodb.core.mapping.Document::class.java)?.let {
when {
it.value.isNotBlank() -> it.value
it.collection.isNotBlank() -> it.collection
else -> null
}
} ?: clazz.simpleName.toCharArray().let {
it[0] = it[0].toLowerCase()
it
}.concatToString()
fun convertToBson(entity: Any): Document {
return when (entity) {
is Document -> entity
is Map<*, *> -> entity.convert()
else -> {
val bson = Document()
entity.javaClass.declaredFields.filter {
it.isAccessible = true
!it.isStatic()
&& !it.isTransient()
//&& it.getAnnotation(Ignore::class.java) == null
&& (it.type != Lazy::class.java || it.get(entity).uncheckedCast<Lazy<*>>().isInitialized())
}.forEach {
injectValue(bson, it.get(entity) ?: return@forEach, it)
}
bson
}
}
}
fun injectValue(bson: Document, value: Any, field: Field) {
when (value) {
is Pair<*, *> -> bson[value.first?.toString() ?: return] = convertToBson(value.second ?: return)
is Map.Entry<*, *> -> bson[value.key?.toString() ?: return] =
convertToBson(value.value ?: return)
else -> bson[MongoName.mongoName(field)] = value.convert() ?: return
}
}
fun <T> fromBson(document: Document, bsonFactory: BsonFactory<T>) = bsonFactory.parse(document)
inline fun <reified T> fromBson(document: Document) = BsonFactory[T::class.java].parse(document)
private fun Iterator<*>.convert(): List<*> {
val list = ArrayList<Any?>()
forEach {
list.add(it.convert() ?: return@forEach)
}
return list
}
private fun Map<*, *>.convert(): Document {
val doc = Document()
forEach { any, u ->
any ?: return@forEach
doc[any.toString()] = u.convert() ?: return@forEach
}
return doc
}
private fun Any?.convert() = when (this) {
null -> null
is Enum<*> -> name
is Boolean, is Number, is String, is Bson, is BsonValue -> this
is Map<*, *> -> this.convert()
is Iterator<*> -> this.convert()
is Iterable<*> -> this.iterator().convert()
else -> convertToBson(this)
}
}

View File

@ -0,0 +1,324 @@
package cn.tursom.database.mongodb.spring
import org.springframework.data.mongodb.core.aggregation.Aggregation
import org.springframework.data.mongodb.core.aggregation.ConditionalOperators
import org.springframework.data.mongodb.core.aggregation.Fields
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation
import org.springframework.data.mongodb.core.aggregation.ProjectionOperation.ProjectionOperationBuilder
import kotlin.reflect.KProperty
@Suppress("unused", "MemberVisibilityCanBePrivate")
object ProjectBuilder : MongoName, BsonConverter {
inline infix operator fun invoke(
operator: ProjectBuilder.() -> ProjectionOperation
): ProjectionOperation = this.operator()
fun project(): ProjectionOperation = Aggregation.project()
fun project(vararg fields: String): ProjectionOperation = Aggregation.project(*fields)
fun project(vararg fields: KProperty<*>): ProjectionOperation =
Aggregation.project(*fields.map { it.mongoName }.toTypedArray())
infix fun ProjectionOperation.and(name: String): ProjectionOperationBuilder = and(name)
infix fun ProjectionOperation.and(name: KProperty<*>): ProjectionOperationBuilder = and(name.mongoName)
infix fun ProjectionOperation.andExclude(fields: KProperty<*>): ProjectionOperation = andExclude(listOf(fields))
fun ProjectionOperation.andExclude(vararg fields: KProperty<*>): ProjectionOperation = andExclude(fields.asList())
infix fun ProjectionOperation.andExclude(fields: Collection<KProperty<*>>): ProjectionOperation =
andExclude(*fields.map { it.mongoName }.toTypedArray())
infix fun ProjectionOperation.andInclude(fields: KProperty<*>): ProjectionOperation = andInclude(listOf(fields))
fun ProjectionOperation.andInclude(vararg fields: KProperty<*>): ProjectionOperation = andInclude(fields.asList())
infix fun ProjectionOperation.andInclude(fields: Collection<KProperty<*>>): ProjectionOperation =
andInclude(*fields.map { it.mongoName }.toTypedArray())
infix fun ProjectionOperationBuilder.nested(fields: Fields): ProjectionOperation = nested(fields)
infix fun ProjectionOperationBuilder.`as`(alias: String): ProjectionOperation = `as`(alias)
infix fun ProjectionOperationBuilder.alias(alias: String): ProjectionOperation = `as`(alias)
infix fun ProjectionOperationBuilder.`as`(alias: KProperty<*>): ProjectionOperation = `as`(alias.mongoName)
infix fun ProjectionOperationBuilder.alias(alias: KProperty<*>): ProjectionOperation = `as`(alias.mongoName)
infix fun ProjectionOperationBuilder.applyCondition(cond: ConditionalOperators.Cond): ProjectionOperation =
applyCondition(cond)
infix fun ProjectionOperationBuilder.applyCondition(ifNull: ConditionalOperators.IfNull): ProjectionOperation =
applyCondition(ifNull)
infix operator fun ProjectionOperationBuilder.plus(number: Number): ProjectionOperationBuilder = plus(number)
infix operator fun ProjectionOperationBuilder.plus(fieldReference: String): ProjectionOperationBuilder =
plus(fieldReference)
infix operator fun ProjectionOperationBuilder.plus(fieldReference: KProperty<*>): ProjectionOperationBuilder =
plus(fieldReference.mongoName)
infix operator fun ProjectionOperationBuilder.minus(number: Number): ProjectionOperationBuilder = minus(number)
infix operator fun ProjectionOperationBuilder.minus(fieldReference: String): ProjectionOperationBuilder =
minus(fieldReference)
infix operator fun ProjectionOperationBuilder.minus(fieldReference: KProperty<*>): ProjectionOperationBuilder =
minus(fieldReference.mongoName)
infix fun ProjectionOperationBuilder.multiply(number: Number): ProjectionOperationBuilder = multiply(number)
infix fun ProjectionOperationBuilder.multiply(fieldReference: String): ProjectionOperationBuilder =
multiply(fieldReference)
infix fun ProjectionOperationBuilder.multiply(fieldReference: KProperty<*>): ProjectionOperationBuilder =
multiply(fieldReference.mongoName)
infix operator fun ProjectionOperationBuilder.times(number: Number): ProjectionOperationBuilder = multiply(number)
infix operator fun ProjectionOperationBuilder.times(fieldReference: String): ProjectionOperationBuilder =
multiply(fieldReference)
infix operator fun ProjectionOperationBuilder.times(fieldReference: KProperty<*>): ProjectionOperationBuilder =
multiply(fieldReference.mongoName)
infix fun ProjectionOperationBuilder.divide(number: Number): ProjectionOperationBuilder = divide(number)
infix fun ProjectionOperationBuilder.divide(fieldReference: String): ProjectionOperationBuilder =
divide(fieldReference)
infix fun ProjectionOperationBuilder.divide(fieldReference: KProperty<*>): ProjectionOperationBuilder =
divide(fieldReference.mongoName)
infix operator fun ProjectionOperationBuilder.div(number: Number): ProjectionOperationBuilder = divide(number)
infix operator fun ProjectionOperationBuilder.div(fieldReference: String): ProjectionOperationBuilder =
divide(fieldReference)
infix operator fun ProjectionOperationBuilder.div(fieldReference: KProperty<*>): ProjectionOperationBuilder =
divide(fieldReference.mongoName)
infix fun ProjectionOperationBuilder.mod(number: Number): ProjectionOperationBuilder = mod(number)
infix fun ProjectionOperationBuilder.mod(fieldReference: String): ProjectionOperationBuilder = mod(fieldReference)
infix fun ProjectionOperationBuilder.mod(fieldReference: KProperty<*>): ProjectionOperationBuilder =
mod(fieldReference.mongoName)
infix fun ProjectionOperationBuilder.cmp(compareValue: Any): ProjectionOperationBuilder = cmp(compareValue)
infix fun ProjectionOperationBuilder.eq(compareValue: Any): ProjectionOperationBuilder = eq(compareValue)
infix fun ProjectionOperationBuilder.gt(compareValue: Any): ProjectionOperationBuilder = gt(compareValue)
infix fun ProjectionOperationBuilder.gte(compareValue: Any): ProjectionOperationBuilder = gte(compareValue)
infix fun ProjectionOperationBuilder.lt(compareValue: Any): ProjectionOperationBuilder = lt(compareValue)
infix fun ProjectionOperationBuilder.lte(compareValue: Any): ProjectionOperationBuilder = lte(compareValue)
infix fun ProjectionOperationBuilder.ne(compareValue: Any): ProjectionOperationBuilder = ne(compareValue)
infix fun ProjectionOperationBuilder.slice(count: Int): ProjectionOperationBuilder = slice(count)
infix fun ProjectionOperationBuilder.log(baseFieldRef: String): ProjectionOperationBuilder = log(baseFieldRef)
infix fun ProjectionOperationBuilder.log(baseFieldRef: KProperty<*>): ProjectionOperationBuilder =
log(baseFieldRef.mongoName)
infix fun ProjectionOperationBuilder.log(base: Number): ProjectionOperationBuilder = log(base)
infix fun ProjectionOperationBuilder.pow(exponentFieldRef: String): ProjectionOperationBuilder = pow(exponentFieldRef)
infix fun ProjectionOperationBuilder.pow(exponentFieldRef: KProperty<*>): ProjectionOperationBuilder =
pow(exponentFieldRef.mongoName)
infix fun ProjectionOperationBuilder.pow(exponent: Number): ProjectionOperationBuilder = pow(exponent)
infix fun ProjectionOperationBuilder.strCaseCmpValueOf(fieldRef: String): ProjectionOperationBuilder =
strCaseCmpValueOf(fieldRef)
infix fun ProjectionOperationBuilder.strCaseCmpValueOf(fieldRef: KProperty<*>): ProjectionOperationBuilder =
strCaseCmpValueOf(fieldRef.mongoName)
@JvmName("concatArrays_")
fun ProjectionOperationBuilder.concatArrays(fields: Collection<String>): ProjectionOperationBuilder =
concatArrays(*fields.toTypedArray())
fun ProjectionOperationBuilder.concatArrays(vararg fields: KProperty<*>): ProjectionOperationBuilder =
concatArrays(*fields.map { it.mongoName }.toTypedArray())
fun ProjectionOperationBuilder.concatArrays(fields: Collection<KProperty<*>>): ProjectionOperationBuilder =
concatArrays(*fields.map { it.mongoName }.toTypedArray())
infix fun String.nested(fields: Fields): ProjectionOperation = project() and this nested (fields)
infix fun String.`as`(alias: String): ProjectionOperation = project() and this `as` (alias)
infix fun String.alias(alias: String): ProjectionOperation = project() and this `as` (alias)
infix fun String.`as`(alias: KProperty<*>): ProjectionOperation = project() and this `as` (alias.mongoName)
infix fun String.alias(alias: KProperty<*>): ProjectionOperation = project() and this `as` (alias.mongoName)
infix fun String.applyCondition(cond: ConditionalOperators.Cond): ProjectionOperation =
project() and this applyCondition (cond)
infix fun String.applyCondition(ifNull: ConditionalOperators.IfNull): ProjectionOperation =
project() and this applyCondition (ifNull)
infix operator fun String.plus(number: Number): ProjectionOperationBuilder = project() and this plus (number)
infix operator fun String.plus(fieldReference: String): ProjectionOperationBuilder =
project() and this plus (fieldReference)
infix operator fun String.plus(fieldReference: KProperty<*>): ProjectionOperationBuilder =
project() and this plus (fieldReference.mongoName)
infix operator fun String.minus(number: Number): ProjectionOperationBuilder = project() and this minus (number)
infix operator fun String.minus(fieldReference: String): ProjectionOperationBuilder =
project() and this minus (fieldReference)
infix operator fun String.minus(fieldReference: KProperty<*>): ProjectionOperationBuilder =
project() and this minus (fieldReference.mongoName)
infix fun String.multiply(number: Number): ProjectionOperationBuilder = project() and this multiply (number)
infix fun String.multiply(fieldReference: String): ProjectionOperationBuilder =
project() and this multiply (fieldReference)
infix fun String.multiply(fieldReference: KProperty<*>): ProjectionOperationBuilder =
project() and this multiply (fieldReference.mongoName)
infix operator fun String.times(number: Number): ProjectionOperationBuilder = project() and this multiply (number)
infix operator fun String.times(fieldReference: String): ProjectionOperationBuilder =
project() and this multiply (fieldReference)
infix operator fun String.times(fieldReference: KProperty<*>): ProjectionOperationBuilder =
project() and this multiply (fieldReference.mongoName)
infix fun String.divide(number: Number): ProjectionOperationBuilder = project() and this divide (number)
infix fun String.divide(fieldReference: String): ProjectionOperationBuilder =
project() and this divide (fieldReference)
infix fun String.divide(fieldReference: KProperty<*>): ProjectionOperationBuilder =
project() and this divide (fieldReference.mongoName)
infix operator fun String.div(number: Number): ProjectionOperationBuilder = project() and this divide (number)
infix operator fun String.div(fieldReference: String): ProjectionOperationBuilder =
project() and this divide (fieldReference)
infix operator fun String.div(fieldReference: KProperty<*>): ProjectionOperationBuilder =
project() and this divide (fieldReference.mongoName)
infix fun String.mod(number: Number): ProjectionOperationBuilder = project() and this mod (number)
infix fun String.mod(fieldReference: String): ProjectionOperationBuilder = project() and this mod (fieldReference)
infix fun String.mod(fieldReference: KProperty<*>): ProjectionOperationBuilder =
project() and this mod (fieldReference.mongoName)
infix fun String.cmp(compareValue: Any): ProjectionOperationBuilder = project() and this cmp (compareValue)
infix fun String.eq(compareValue: Any): ProjectionOperationBuilder = project() and this eq (compareValue)
infix fun String.gt(compareValue: Any): ProjectionOperationBuilder = project() and this gt (compareValue)
infix fun String.gte(compareValue: Any): ProjectionOperationBuilder = project() and this gte (compareValue)
infix fun String.lt(compareValue: Any): ProjectionOperationBuilder = project() and this lt (compareValue)
infix fun String.lte(compareValue: Any): ProjectionOperationBuilder = project() and this lte (compareValue)
infix fun String.ne(compareValue: Any): ProjectionOperationBuilder = project() and this ne (compareValue)
infix fun String.slice(count: Int): ProjectionOperationBuilder = project() and this slice (count)
fun String.slice(count: Int, offset: Int): ProjectionOperationBuilder = (project() and this).slice(count, offset)
infix fun String.log(baseFieldRef: String): ProjectionOperationBuilder = project() and this log (baseFieldRef)
infix fun String.log(baseFieldRef: KProperty<*>): ProjectionOperationBuilder =
project() and this log (baseFieldRef.mongoName)
infix fun String.log(base: Number): ProjectionOperationBuilder = project() and this log (base)
infix fun String.pow(exponentFieldRef: String): ProjectionOperationBuilder = project() and this pow (exponentFieldRef)
infix fun String.pow(exponentFieldRef: KProperty<*>): ProjectionOperationBuilder =
project() and this pow (exponentFieldRef.mongoName)
infix fun String.pow(exponent: Number): ProjectionOperationBuilder = project() and this pow (exponent)
infix fun String.strCaseCmpValueOf(fieldRef: String): ProjectionOperationBuilder =
project() and this strCaseCmpValueOf (fieldRef)
infix fun String.strCaseCmpValueOf(fieldRef: KProperty<*>): ProjectionOperationBuilder =
project() and this strCaseCmpValueOf (fieldRef.mongoName)
infix fun KProperty<*>.nested(fields: Fields): ProjectionOperation = project() and this nested (fields)
infix fun KProperty<*>.`as`(alias: String): ProjectionOperation = project() and this `as` (alias)
infix fun KProperty<*>.alias(alias: String): ProjectionOperation = project() and this `as` (alias)
infix fun KProperty<*>.`as`(alias: KProperty<*>): ProjectionOperation = project() and this `as` (alias.mongoName)
infix fun KProperty<*>.alias(alias: KProperty<*>): ProjectionOperation = project() and this `as` (alias.mongoName)
infix fun KProperty<*>.applyCondition(cond: ConditionalOperators.Cond): ProjectionOperation =
project() and this applyCondition (cond)
infix fun KProperty<*>.applyCondition(ifNull: ConditionalOperators.IfNull): ProjectionOperation =
project() and this applyCondition (ifNull)
infix operator fun KProperty<*>.plus(number: Number): ProjectionOperationBuilder = project() and this plus (number)
infix operator fun KProperty<*>.plus(fieldReference: String): ProjectionOperationBuilder =
project() and this plus (fieldReference)
infix operator fun KProperty<*>.plus(fieldReference: KProperty<*>): ProjectionOperationBuilder =
project() and this plus (fieldReference.mongoName)
infix operator fun KProperty<*>.minus(number: Number): ProjectionOperationBuilder = project() and this minus (number)
infix operator fun KProperty<*>.minus(fieldReference: String): ProjectionOperationBuilder =
project() and this minus (fieldReference)
infix operator fun KProperty<*>.minus(fieldReference: KProperty<*>): ProjectionOperationBuilder =
project() and this minus (fieldReference.mongoName)
infix fun KProperty<*>.multiply(number: Number): ProjectionOperationBuilder = project() and this multiply (number)
infix fun KProperty<*>.multiply(fieldReference: String): ProjectionOperationBuilder =
project() and this multiply (fieldReference)
infix fun KProperty<*>.multiply(fieldReference: KProperty<*>): ProjectionOperationBuilder =
project() and this multiply (fieldReference.mongoName)
infix operator fun KProperty<*>.times(number: Number): ProjectionOperationBuilder =
project() and this multiply (number)
infix operator fun KProperty<*>.times(fieldReference: String): ProjectionOperationBuilder =
project() and this multiply (fieldReference)
infix operator fun KProperty<*>.times(fieldReference: KProperty<*>): ProjectionOperationBuilder =
project() and this multiply (fieldReference.mongoName)
infix fun KProperty<*>.divide(number: Number): ProjectionOperationBuilder = project() and this divide (number)
infix fun KProperty<*>.divide(fieldReference: String): ProjectionOperationBuilder =
project() and this divide (fieldReference)
infix fun KProperty<*>.divide(fieldReference: KProperty<*>): ProjectionOperationBuilder =
project() and this divide (fieldReference.mongoName)
infix operator fun KProperty<*>.div(number: Number): ProjectionOperationBuilder = project() and this divide (number)
infix operator fun KProperty<*>.div(fieldReference: String): ProjectionOperationBuilder =
project() and this divide (fieldReference)
infix operator fun KProperty<*>.div(fieldReference: KProperty<*>): ProjectionOperationBuilder =
project() and this divide (fieldReference.mongoName)
infix fun KProperty<*>.mod(number: Number): ProjectionOperationBuilder = project() and this mod (number)
infix fun KProperty<*>.mod(fieldReference: String): ProjectionOperationBuilder =
project() and this mod (fieldReference)
infix fun KProperty<*>.mod(fieldReference: KProperty<*>): ProjectionOperationBuilder =
project() and this mod (fieldReference.mongoName)
infix fun KProperty<*>.cmp(compareValue: Any): ProjectionOperationBuilder = project() and this cmp (compareValue)
infix fun KProperty<*>.eq(compareValue: Any): ProjectionOperationBuilder = project() and this eq (compareValue)
infix fun KProperty<*>.gt(compareValue: Any): ProjectionOperationBuilder = project() and this gt (compareValue)
infix fun KProperty<*>.gte(compareValue: Any): ProjectionOperationBuilder = project() and this gte (compareValue)
infix fun KProperty<*>.lt(compareValue: Any): ProjectionOperationBuilder = project() and this lt (compareValue)
infix fun KProperty<*>.lte(compareValue: Any): ProjectionOperationBuilder = project() and this lte (compareValue)
infix fun KProperty<*>.ne(compareValue: Any): ProjectionOperationBuilder = project() and this ne (compareValue)
infix fun KProperty<*>.slice(count: Int): ProjectionOperationBuilder = project() and this slice (count)
fun KProperty<*>.slice(count: Int, offset: Int): ProjectionOperationBuilder =
(project() and this).slice(count, offset)
infix fun KProperty<*>.log(baseFieldRef: String): ProjectionOperationBuilder = project() and this log (baseFieldRef)
infix fun KProperty<*>.log(baseFieldRef: KProperty<*>): ProjectionOperationBuilder =
project() and this log (baseFieldRef.mongoName)
infix fun KProperty<*>.log(base: Number): ProjectionOperationBuilder = project() and this log (base)
infix fun KProperty<*>.pow(exponentFieldRef: String): ProjectionOperationBuilder =
project() and this pow (exponentFieldRef)
infix fun KProperty<*>.pow(exponentFieldRef: KProperty<*>): ProjectionOperationBuilder =
project() and this pow (exponentFieldRef.mongoName)
infix fun KProperty<*>.pow(exponent: Number): ProjectionOperationBuilder = project() and this pow (exponent)
infix fun KProperty<*>.strCaseCmpValueOf(fieldRef: String): ProjectionOperationBuilder =
project() and this strCaseCmpValueOf (fieldRef)
infix fun KProperty<*>.strCaseCmpValueOf(fieldRef: KProperty<*>): ProjectionOperationBuilder =
project() and this strCaseCmpValueOf (fieldRef.mongoName)
@JvmName("concatArrays_")
fun String.concatArrays(fields: Collection<String>): ProjectionOperationBuilder =
(project() and this).concatArrays(*fields.toTypedArray())
fun String.concatArrays(vararg fields: KProperty<*>): ProjectionOperationBuilder =
(project() and this).concatArrays(*fields.map { it.mongoName }.toTypedArray())
fun String.concatArrays(fields: Collection<KProperty<*>>): ProjectionOperationBuilder =
(project() and this).concatArrays(*fields.map { it.mongoName }.toTypedArray())
}

View File

@ -0,0 +1,331 @@
package cn.tursom.database.mongodb.spring
import cn.tursom.core.uncheckedCast
import org.bson.BsonRegularExpression
import org.bson.Document
import org.bson.types.ObjectId
import org.springframework.data.domain.Example
import org.springframework.data.domain.Sort
import org.springframework.data.geo.Circle
import org.springframework.data.geo.Point
import org.springframework.data.geo.Shape
import org.springframework.data.mongodb.core.geo.GeoJson
import org.springframework.data.mongodb.core.query.Criteria
import org.springframework.data.mongodb.core.query.Field
import org.springframework.data.mongodb.core.query.Query
import org.springframework.data.mongodb.core.schema.MongoJsonSchema
import java.util.*
import java.util.regex.Pattern
import kotlin.reflect.KProperty
@Suppress("MemberVisibilityCanBePrivate", "unused")
class QueryBuilder : MongoName {
companion object {
const val where = "\$where"
private val criteriaField: java.lang.reflect.Field? = try {
Criteria::class.java.getDeclaredField("criteria")
} catch (e: Exception) {
null
}
private val isValueField: java.lang.reflect.Field? = try {
Criteria::class.java.getDeclaredField("isValue")
} catch (e: Exception) {
null
}
private val criteriaChainField: java.lang.reflect.Field? = try {
Criteria::class.java.getDeclaredField("criteriaChain")
} catch (e: Exception) {
null
}
init {
criteriaField?.isAccessible = true
isValueField?.isAccessible = true
criteriaChainField?.isAccessible = true
}
inline infix operator fun invoke(operator: QueryBuilder.() -> Unit): Query = QueryBuilder().let { builder ->
builder.operator()
val query = Query(builder.and)
builder.queryHandler.forEach { handler ->
query.handler()
}
query
}
infix fun queryObject(operator: QueryBuilder.() -> Unit): Document = this(operator).queryObject
infix fun fieldsObject(operator: QueryBuilder.() -> Unit): Document = this(operator).fieldsObject
infix fun sortObject(operator: QueryBuilder.() -> Unit): Document = this(operator).sortObject
infix fun criteria(operator: QueryBuilder.() -> Unit): Criteria = QueryBuilder().apply(operator).and
}
val queryHandler = LinkedList<Query.() -> Unit>()
private var uniqueField = true
private val fieldSet = HashSet<String>()
val and: Criteria
get() = when (criteriaList.size) {
0 -> Criteria()
1 -> criteriaList.first()
else -> if (uniqueField && criteriaField != null && isValueField != null) {
var base = Criteria()
criteriaList.forEach {
if (it.key != null) {
base = base.and(it.key!!)
criteriaField.set(base, criteriaField.get(it))
isValueField.set(base, isValueField.get(it))
} else {
criteriaChainField?.get(base)?.uncheckedCast<MutableList<Any>>()
?.addAll(criteriaChainField.get(it).uncheckedCast())
}
}
base
} else {
Criteria().andOperator(*criteriaList.toTypedArray())
}
}
val or: Criteria
get() = when (criteriaList.size) {
0 -> Criteria()
1 -> criteriaList.first()
else -> Criteria().orOperator(*criteriaList.toTypedArray())
}
private val criteriaList = ArrayList<Criteria>()
fun query(handler: Query.() -> Unit) {
queryHandler.add(handler)
}
fun String.asJs() = where equal this
fun String.asObjectId() = ObjectId(this)
private fun String.asWhere() = Criteria.where(this).also { uniqueField = uniqueField && fieldSet.add(this) }
private fun KProperty<*>.asWhere() =
mongoName.asWhere().also { uniqueField = uniqueField && fieldSet.add(mongoName) }
//infix fun Criteria.or(operator: QueryBuilder.() -> Unit) {
// criteriaList.add(QueryBuilder().also(operator).criteria)
//}
fun or(operator: QueryBuilder.() -> Unit) {
QueryBuilder().also(operator).or.also(criteriaList::add)
}
fun and(operator: QueryBuilder.() -> Unit) {
QueryBuilder().also(operator).and.also(criteriaList::add)
}
infix fun KProperty<*>.gt(where: Any) = asWhere() gt where // >
infix fun KProperty<*>.gte(where: Any) = asWhere() gte where // >=
fun KProperty<*>.`in`(vararg where: Any?) = asWhere() `in` where // contains
infix fun KProperty<*>.`in`(where: Collection<Any?>) = asWhere() `in` (where) // contains
infix fun KProperty<*>.`is`(where: Any?) = asWhere() `is` where // ==
infix fun KProperty<*>.ne(where: Any?) = asWhere() ne where // !=
infix fun KProperty<*>.lt(where: Any) = asWhere() lt where // <
infix fun KProperty<*>.lte(where: Any) = asWhere() lte where // <=
fun KProperty<*>.nin(vararg where: Any?) = asWhere() nin where // not contains
infix fun KProperty<*>.nin(where: Collection<Any?>) = asWhere() nin where // not contains
infix fun KProperty<*>.all(where: Any?) = asWhere() all where
infix fun KProperty<*>.all(where: Collection<Any?>) = asWhere() all where
infix fun KProperty<*>.size(s: Int) = asWhere() size s
infix fun KProperty<*>.exists(b: Boolean) = asWhere() exists b
infix fun KProperty<*>.type(t: Int) = asWhere() type t
operator fun KProperty<*>.not() = asWhere().not().last(criteriaList::add)
infix fun KProperty<*>.regex(re: String) = asWhere() regex re
infix fun KProperty<*>.regex(pattern: Pattern) = asWhere() regex pattern
infix fun KProperty<*>.regex(pattern: Regex) = asWhere() regex pattern
infix fun KProperty<*>.regex(regex: BsonRegularExpression) = asWhere() regex regex
infix fun KProperty<*>.withinSphere(circle: Circle) = asWhere() withinSphere circle
infix fun KProperty<*>.within(shape: Shape) = asWhere() within shape
infix fun KProperty<*>.near(point: Point) = asWhere() near point
infix fun KProperty<*>.nearSphere(point: Point) = asWhere() nearSphere point
infix fun KProperty<*>.intersects(point: GeoJson<*>) = asWhere() intersects point
infix fun KProperty<*>.maxDistance(maxDistance: Double) = asWhere() maxDistance maxDistance
infix fun KProperty<*>.minDistance(maxDistance: Double) = asWhere() minDistance maxDistance
infix fun KProperty<*>.elemMatch(c: QueryBuilder.() -> Unit) = asWhere() elemMatch c
infix fun KProperty<*>.alike(sample: Example<*>) = asWhere() alike sample
infix fun KProperty<*>.andDocumentStructureMatches(schema: MongoJsonSchema) =
asWhere() andDocumentStructureMatches schema
infix fun KProperty<*>.equal(where: Any?) = asWhere() equal where
infix fun KProperty<*>.eq(where: Any?) = asWhere() equal where
infix fun KProperty<*>.equal(where: QueryBuilder.() -> Unit) = asWhere() equal QueryBuilder(where).queryObject
infix fun KProperty<*>.eq(where: QueryBuilder.() -> Unit) = asWhere() equal QueryBuilder(where).queryObject
infix fun String.gt(where: Any) = asWhere() gt where // >
infix fun String.gte(where: Any) = asWhere() gte where // >=
fun String.`in`(vararg where: Any?) = asWhere() `in` where // contains
infix fun String.`in`(where: Collection<Any?>) = asWhere() `in` where // contains
infix fun String.`is`(where: Any?) = asWhere() `is` where // ==
infix fun String.ne(where: Any?) = asWhere() ne where // !=
infix fun String.lt(where: Any) = asWhere() lt where // <
infix fun String.lte(where: Any) = asWhere() lte where // <=
fun String.nin(vararg where: Any?) = asWhere() nin where // not contains
infix fun String.nin(where: Collection<Any?>) = asWhere() nin where // not contains
infix fun String.all(where: Any?) = asWhere() all where
infix fun String.all(where: Collection<Any?>) = asWhere() all where
infix fun String.size(s: Int) = asWhere() size s
infix fun String.exists(b: Boolean) = asWhere() exists b
infix fun String.type(t: Int) = asWhere() type t
operator fun String.not() = asWhere().not().last(criteriaList::add)
infix fun String.regex(re: String) = asWhere() regex re
infix fun String.regex(pattern: Pattern) = asWhere() regex pattern
infix fun String.regex(pattern: Regex) = asWhere() regex pattern.toPattern()
infix fun String.regex(regex: BsonRegularExpression) = asWhere() regex regex
infix fun String.withinSphere(circle: Circle) = asWhere() withinSphere circle
infix fun String.within(shape: Shape) = asWhere() within shape
infix fun String.near(point: Point) = asWhere() near point
infix fun String.nearSphere(point: Point) = asWhere() nearSphere point
infix fun String.intersects(point: GeoJson<*>) = asWhere() intersects point
infix fun String.maxDistance(maxDistance: Double) = asWhere() maxDistance maxDistance
infix fun String.minDistance(maxDistance: Double) = asWhere() minDistance maxDistance
infix fun String.elemMatch(c: QueryBuilder.() -> Unit) = asWhere() elemMatch c
infix fun String.alike(sample: Example<*>) = asWhere() alike sample
infix fun String.andDocumentStructureMatches(schema: MongoJsonSchema) = asWhere() andDocumentStructureMatches schema
infix fun String.equal(where: Any?) = asWhere() equal where
infix fun String.eq(where: Any?) = asWhere() equal where
infix fun String.equal(where: QueryBuilder.() -> Unit) = asWhere() equal QueryBuilder(where).queryObject
infix fun String.eq(where: QueryBuilder.() -> Unit) = asWhere() equal QueryBuilder(where).queryObject
private infix fun Criteria.gt(where: Any) = gt(where).last(criteriaList::add)
private infix fun Criteria.gte(where: Any) = gte(where).last(criteriaList::add)
private infix fun Criteria.`in`(where: Array<out Any?>) = `in`(where).last(criteriaList::add)
private infix fun Criteria.`in`(where: Collection<Any?>) = `in`(where).last(criteriaList::add)
private infix fun Criteria.`is`(where: Any?) = `is`(where).last(criteriaList::add)
private infix fun Criteria.ne(where: Any?) = ne(where).last(criteriaList::add)
private infix fun Criteria.lt(where: Any) = lt(where).last(criteriaList::add)
private infix fun Criteria.lte(where: Any) = lte(where).last(criteriaList::add)
private infix fun Criteria.nin(where: Array<out Any?>) = nin(where).last(criteriaList::add)
private infix fun Criteria.nin(where: Collection<Any?>) = nin(where).last(criteriaList::add)
private infix fun Criteria.all(where: Any?) = all(where).last(criteriaList::add)
private infix fun Criteria.all(where: Collection<Any?>) = all(where).last(criteriaList::add)
private infix fun Criteria.size(s: Int) = size(s).last(criteriaList::add)
private infix fun Criteria.exists(b: Boolean) = exists(b).last(criteriaList::add)
private infix fun Criteria.type(t: Int) = type(t).last(criteriaList::add)
//private operator fun Criteria.not() = not().last(criteriaList::add)
private infix fun Criteria.regex(re: String) = regex(re).last(criteriaList::add)
private infix fun Criteria.regex(pattern: Pattern) = regex(pattern).last(criteriaList::add)
private infix fun Criteria.regex(pattern: Regex) = regex(pattern.toPattern()).last(criteriaList::add)
private infix fun Criteria.regex(regex: BsonRegularExpression) = regex(regex).last(criteriaList::add)
private infix fun Criteria.withinSphere(circle: Circle) = withinSphere(circle).last(criteriaList::add)
private infix fun Criteria.within(shape: Shape) = within(shape).last(criteriaList::add)
private infix fun Criteria.near(point: Point) = near(point).last(criteriaList::add)
private infix fun Criteria.nearSphere(point: Point) = nearSphere(point).last(criteriaList::add)
private infix fun Criteria.intersects(point: GeoJson<*>) = intersects(point).last(criteriaList::add)
private infix fun Criteria.maxDistance(maxDistance: Double) = maxDistance(maxDistance).last(criteriaList::add)
private infix fun Criteria.minDistance(maxDistance: Double) = minDistance(maxDistance).last(criteriaList::add)
private infix fun Criteria.elemMatch(c: QueryBuilder.() -> Unit) =
elemMatch(QueryBuilder criteria c).last(criteriaList::add)
private infix fun Criteria.alike(sample: Example<*>) = alike(sample).last(criteriaList::add)
private infix fun Criteria.andDocumentStructureMatches(schema: MongoJsonSchema) =
andDocumentStructureMatches(schema).last(criteriaList::add)
private infix fun Criteria.equal(where: Any?) = `is`(where).last(criteriaList::add)
/**
* 根据 builder 所指定的规则排序
*/
infix fun Query.sort(builder: SortBuilder.() -> Unit) = with(SortBuilder(builder))
/**
* 取以 field 为基准逆序排序的第一个元素
*/
infix fun Query.last(field: KProperty<*>) = apply {
sort { field order Sort.Direction.DESC }
limit(1)
}
/**
* 取以所有 field 为基准逆序排序的第一个元素
*/
fun Query.last(vararg field: KProperty<*>) = last(field.iterator())
/**
* 取以所有 field 为基准逆序排序的第一个元素
*/
@JvmName("lastFields")
fun Query.last(fields: Iterable<KProperty<*>>) = last(fields.iterator())
/**
* 取以所有 field 为基准逆序排序的第一个元素
*/
@JvmName("lastFields")
fun Query.last(fields: Iterator<KProperty<*>>) = apply {
sort { fields.forEach { it order Sort.Direction.DESC } }
limit(1)
}
/**
* 取以 field 为基准顺序排序的第一个元素
*/
infix fun Query.first(field: KProperty<*>) = apply {
sort { field order Sort.Direction.ASC }
limit(1)
}
/**
* 取以所有 field 为基准顺序排序的第一个元素
*/
fun Query.first(vararg field: KProperty<*>) = first(field.iterator())
/**
* 取以所有 field 为基准顺序排序的第一个元素
*/
@JvmName("firstFields")
fun Query.first(fields: Iterable<KProperty<*>>) = first(fields.iterator())
/**
* 取以所有 field 为基准顺序排序的第一个元素
*/
@JvmName("firstFields")
fun Query.first(fields: Iterator<KProperty<*>>) = apply {
sort { fields.forEach { it order Sort.Direction.ASC } }
limit(1)
}
infix fun Query.last(field: String) = apply {
sort { field order Sort.Direction.DESC }
limit(1)
}
fun Query.last(vararg field: String) = last(field.iterator())
fun Query.last(fields: Iterable<String>) = last(fields.iterator())
fun Query.last(fields: Iterator<String>) = apply {
sort { fields.forEach { it order Sort.Direction.DESC } }
limit(1)
}
infix fun Query.first(field: String) = apply {
sort { field order Sort.Direction.ASC }
limit(1)
}
fun Query.first(vararg field: String) = first(field.iterator())
fun Query.first(fields: Iterable<String>) = first(fields.iterator())
fun Query.first(fields: Iterator<String>) = apply {
sort { fields.forEach { it order Sort.Direction.ASC } }
limit(1)
}
val Query.fields get() = fields()
infix fun Field.include(key: KProperty<*>) = include(key.mongoName)
infix fun Field.exclude(key: KProperty<*>) = exclude(key.mongoName)
fun Field.slice(key: KProperty<*>, size: Int) = slice(key.mongoName, size)
fun Field.slice(key: KProperty<*>, offset: Int, size: Int) = slice(key.mongoName, offset, size)
fun Field.elemMatch(key: KProperty<*>, elemMatchCriteria: Criteria) = elemMatch(key.mongoName, elemMatchCriteria)
fun Field.position(field: KProperty<*>, value: Int) = position(mongoName(field), value)
}

View File

@ -0,0 +1,41 @@
package cn.tursom.database.mongodb.spring
import org.springframework.data.domain.Sort
import org.springframework.data.mongodb.core.aggregation.SortOperation
import kotlin.reflect.KProperty
@Suppress("MemberVisibilityCanBePrivate", "unused")
class SortBuilder : MongoName, BsonConverter {
companion object {
operator fun invoke(action: SortBuilder.() -> Unit): Sort {
val builder = SortBuilder()
builder.action()
return builder.sort!!
}
}
private var sort: Sort? = null
val sortOperation get() = SortOperation(sort!!)
operator fun invoke(action: SortBuilder.() -> Unit) = this.action()
infix fun String.order(direction: Sort.Direction): Sort = Sort.by(Sort.Order(direction, this)).apply {
updateSort(this) { this.and(it) }
}
infix fun KProperty<*>.order(direction: Sort.Direction) = mongoName order direction
fun by(property: String) = Sort.by(property).apply {
updateSort(this) { this.and(it) }
}
fun by(property: KProperty<*>) = by(property.mongoName)
private fun updateSort(sort: Sort, action: (Sort) -> Sort) {
if (this.sort == null) {
this.sort = sort
} else {
this.sort = action(this.sort!!)
}
}
}

View File

@ -0,0 +1,109 @@
package cn.tursom.database.mongodb.spring
import org.bson.Document
import org.springframework.data.mongodb.core.query.Update
import kotlin.reflect.KProperty
@Suppress("unused", "MemberVisibilityCanBePrivate", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "HasPlatformType")
class UpdateBuilder(val update: Update = Update()) : MongoName, BsonConverter {
companion object {
inline infix operator fun invoke(operator: UpdateBuilder.() -> Unit): Update =
UpdateBuilder(Update()).also(operator).update
inline infix fun updateObject(operator: UpdateBuilder.() -> Unit): Document =
UpdateBuilder(Update()).also(operator).update.updateObject
private val addMultiFieldOperationMethod = Update::class.java.getDeclaredMethod(
"addMultiFieldOperation",
String::class.java,
String::class.java,
Any::class.java
)
init {
addMultiFieldOperationMethod.isAccessible = true
}
private fun Update.addMultiFieldOperation(operator: String, key: String, value: Any?) {
addMultiFieldOperationMethod.invoke(this, operator, key, value)
}
}
infix fun String.set(value: Any?) = update.set(this, value?.bsonValue())
infix fun String.setOnInsert(value: Any?) = update.setOnInsert(this, value?.bsonValue())
fun String.unset() = update.unset(this)
infix fun String.inc(inc: Number) = update.inc(this, inc)
fun String.inc() = update.inc(this, 1)
infix fun String.push(value: Any?) = update.push(this, value?.bsonValue())
fun String.push() = update.push(this)
@Suppress("DEPRECATION")
@Deprecated(
"as of MongoDB 2.4. Removed in MongoDB 3.6. Use {@link #push(String) \$push \$each} instead.",
ReplaceWith("push")
)
infix fun String.pushAll(values: Array<out Any?>) = update.pushAll(this, values)
fun String.addToSet() = update.addToSet(this)
infix fun String.addToSet(value: Any?) = update.addToSet(this, value?.bsonValue())
infix fun String.pop(pos: Update.Position) = update.pop(this, pos)
infix fun String.pull(value: Any?) = update.pull(this, value?.bsonValue())
infix fun String.pull(query: QueryBuilder.() -> Unit) = update.pull(this, QueryBuilder queryObject query)
infix fun String.pullAll(values: Array<out Any?>) = update.pullAll(this, values)
infix fun String.pullAll(values: Iterable<Any?>) = update.addMultiFieldOperation("\$pullAll", this, values)
infix fun String.rename(newName: String) = update.rename(this, newName)
fun String.currentDate() = update.currentDate(this)
fun String.currentTimestamp() = update.currentTimestamp(this)
infix fun String.multiply(multiplier: Number) = update.multiply(this, multiplier)
infix fun String.max(value: Any) = update.max(this, value.bsonValue())
infix fun String.min(value: Any) = update.min(this, value.bsonValue())
fun String.bitwise() = update.bitwise(this)
fun String.modifies() = update.modifies(this)
infix fun <T> KProperty<T?>.set(value: T) = mongoName set value
infix fun <T> KProperty<T?>.update(value: T) = mongoName set value
infix fun <T> KProperty<T?>.setOnInsert(value: T) = mongoName setOnInsert value
fun KProperty<*>.unset() = mongoName.unset()
infix fun KProperty<*>.inc(inc: Number) = mongoName inc inc
fun KProperty<*>.inc() = mongoName inc (1)
infix fun KProperty<*>.push(value: Any) = mongoName push value
fun KProperty<*>.push() = mongoName.push()
@Suppress("DEPRECATION")
@Deprecated(
"as of MongoDB 2.4. Removed in MongoDB 3.6. Use {@link #push(String) \$push \$each} instead.",
ReplaceWith("push")
)
infix fun <T> KProperty<T>.pushAll(values: Array<T>) = mongoName pushAll values
fun KProperty<*>.addToSet() = mongoName.addToSet()
infix fun <T : Any> KProperty<Iterable<T>>.addToSet(value: T) = mongoName addToSet value
@JvmName("addToSetArray")
infix fun <T> KProperty<Array<T>>.addToSet(value: T) = mongoName addToSet value
infix fun KProperty<*>.pop(pos: Update.Position) = mongoName pop pos
infix fun <T> KProperty<Iterable<T>>.pull(value: T) = mongoName pull value
@JvmName("pullArray")
infix fun <T> KProperty<Array<T>>.pull(value: T) = mongoName pull value
infix fun KProperty<*>.pull(query: QueryBuilder.() -> Unit) =
update.pull(mongoName, QueryBuilder queryObject query)
infix fun <T> KProperty<Array<T>>.pullAll(values: Array<out T>) = mongoName pullAll values
infix fun <T> KProperty<Array<T>>.pullAll(values: Iterable<T>) = mongoName pullAll values
@JvmName("pullAllIterable")
infix fun <T> KProperty<Iterable<T>>.pullAll(values: Array<out T>) = mongoName pullAll values
@JvmName("pullAllIterable")
infix fun <T> KProperty<Iterable<T>>.pullAll(values: Iterable<T>) = mongoName pullAll values
infix fun KProperty<*>.rename(newName: String) = mongoName rename newName
fun KProperty<*>.currentDate() = mongoName.currentDate()
fun KProperty<*>.currentTimestamp() = mongoName.currentTimestamp()
infix fun KProperty<*>.multiply(multiplier: Number) = mongoName multiply multiplier
infix fun KProperty<*>.max(value: Any) = mongoName max value
infix fun KProperty<*>.min(value: Any) = mongoName min value
fun KProperty<*>.bitwise() = mongoName.bitwise()
fun KProperty<*>.modifies() = mongoName.modifies()
}

View File

@ -0,0 +1,48 @@
package cn.tursom.database.mongodb.spring
import cn.tursom.core.uncheckedCast
import com.mongodb.client.model.Collation
import com.mongodb.client.model.UpdateOptions
import org.bson.conversions.Bson
@Suppress("MemberVisibilityCanBePrivate", "unused")
class UpdateOption {
companion object {
operator fun invoke(builder: UpdateOption.() -> Unit): UpdateOptions = UpdateOption().apply(builder).updateOptions
}
val updateOptions = UpdateOptions()
var arrayFilters: MutableList<Bson>?
@JvmName("arrayFiltersGetter")
get() = updateOptions.arrayFilters.uncheckedCast()
set(value) {
updateOptions.arrayFilters(value)
}
fun getArrayFilters(): MutableList<Bson> {
var list = arrayFilters
if (list == null) {
list = ArrayList()
arrayFilters = list
}
return list
}
fun arrayFilter(builder: QueryBuilder.() -> Unit) = getArrayFilters().add(QueryBuilder queryObject builder)
var upsert: Boolean
get() = updateOptions.isUpsert
set(value) {
updateOptions.upsert(value)
}
var bypassDocumentValidation: Boolean?
get() = updateOptions.bypassDocumentValidation
set(value) {
updateOptions.bypassDocumentValidation(value)
}
var collation: Collation?
get() = updateOptions.collation
set(value) {
updateOptions.collation(value)
}
}

View File

@ -0,0 +1,5 @@
package cn.tursom.database.mongodb.spring.function
fun interface Function1<R, T> : SerializedFunction {
operator fun T.invoke(): R
}

View File

@ -0,0 +1,51 @@
package cn.tursom.database.mongodb.spring.function
import java.io.Serializable
import java.lang.invoke.SerializedLambda
import java.lang.reflect.Field
import java.lang.reflect.Method
interface SerializedFunction : Serializable
private val getterRegex = Regex("^get[A-Z].*")
private val booleanGetterRegex = Regex("^is[A-Z].*")
val SerializedFunction.serializedLambda: SerializedLambda
get() {
val writeReplace = javaClass.getDeclaredMethod("writeReplace")
writeReplace.isAccessible = true
return writeReplace.invoke(this) as SerializedLambda
}
val SerializedFunction.implClassName: String get() = serializedLambda.implClass.replace('/', '.')
val SerializedFunction.implClass: Class<*> get() = Class.forName(implClassName)
val SerializedFunction.implMethodName: String get() = serializedLambda.implMethodName
val SerializedFunction.implMethod: Method get() = implClass.getMethod(serializedLambda.implMethodName)
val SerializedFunction.fieldName: String?
get() {
val methodName = implMethodName
return getFieldNameFromGetterName(methodName)
}
val SerializedFunction.field: Field?
get() {
val fieldName = fieldName ?: return null
val clazz = implClass
return clazz.getDeclaredField(fieldName)
}
fun getFieldNameFromGetterName(getterName: String): String? = when {
getterRegex.matches(getterName) -> {
val chars = getterName.toCharArray()
chars[3] = Character.toLowerCase(chars[3])
String(chars, 3, chars.size - 3)
}
booleanGetterRegex.matches(getterName) -> {
val chars = getterName.toCharArray()
chars[2] = Character.toLowerCase(chars[2])
String(chars, 2, chars.size - 2)
}
else -> getterName
}

View File

@ -0,0 +1,87 @@
@file:Suppress("unused")
package cn.tursom.database.mongodb.spring
import com.mongodb.client.result.DeleteResult
import com.mongodb.client.result.UpdateResult
import org.springframework.data.mongodb.core.MongoTemplate
import org.springframework.data.mongodb.core.query.Criteria
import org.springframework.data.mongodb.core.query.Field
import org.springframework.data.mongodb.core.query.Query
import org.springframework.data.mongodb.core.query.Update
import kotlin.reflect.KProperty
inline fun <reified T : Any> MongoTemplate.count(query: Query) = count(query, T::class.java)
inline fun <reified T : Any> MongoTemplate.count(
noinline operator: QueryBuilder.() -> Unit,
) = count(QueryBuilder(operator), T::class.java)
inline fun <reified T : Any> MongoTemplate.find(query: Query): List<T> = find(query, T::class.java)
inline fun <reified T : Any> MongoTemplate.find(
noinline operator: QueryBuilder.() -> Unit,
): List<T> = find(QueryBuilder(operator), T::class.java)
inline fun <reified T : Any> MongoTemplate.findOne(query: Query): T? = findOne(query, T::class.java)
inline fun <reified T : Any> MongoTemplate.findOne(
noinline operator: QueryBuilder.() -> Unit,
): T? = findOne(QueryBuilder(operator), T::class.java)
//inline fun <T, R> T.use(action: (T) -> R): T {
// action(this)
// return this
//}
inline fun <T, R> T.last(action: (T) -> R) {
action(this)
}
inline fun <reified T : Any> MongoTemplate.upsert(
query: Query,
update: Update,
): UpdateResult = upsert(query, update, T::class.java)
inline fun <reified T : Any> MongoTemplate.upsert(
collectionName: String,
query: Query,
update: Update,
): UpdateResult = upsert(query, update, T::class.java, collectionName)
inline fun <reified T : Any> MongoTemplate.updateFirst(
query: Query,
update: Update,
): UpdateResult = updateFirst(query, update, T::class.java)
inline fun <reified T : Any> MongoTemplate.updateFirst(
collectionName: String,
query: Query,
update: Update,
): UpdateResult = updateFirst(query, update, T::class.java, collectionName)
inline fun <reified T : Any> MongoTemplate.updateMulti(
query: Query,
update: Update,
): UpdateResult = updateMulti(query, update, T::class.java)
inline fun <reified T : Any> MongoTemplate.updateMulti(
collectionName: String,
query: Query,
update: Update,
): UpdateResult = updateMulti(query, update, T::class.java, collectionName)
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
inline fun <reified T : Any> MongoTemplate.remove(
queryBuilder: QueryBuilder.() -> Unit,
): DeleteResult = remove(QueryBuilder(queryBuilder), T::class.java)
infix fun Query.sort(builder: SortBuilder.() -> Unit) {
with(SortBuilder(builder))
}
infix fun Field.include(key: KProperty<*>) = include(MongoName.mongoName(key))
infix fun Field.exclude(key: KProperty<*>) = exclude(MongoName.mongoName(key))
fun Field.slice(key: KProperty<*>, size: Int) = slice(MongoName.mongoName(key), size)
fun Field.slice(key: KProperty<*>, offset: Int, size: Int) = slice(MongoName.mongoName(key), offset, size)
fun Field.elemMatch(key: KProperty<*>, elemMatchCriteria: Criteria) =
elemMatch(MongoName.mongoName(key), elemMatchCriteria)
fun Field.position(field: KProperty<*>, value: Int) = position(MongoName.mongoName(field), value)

View File

@ -0,0 +1,13 @@
package cn.tursom.database.mongodb.spring
class UpdateBuilderTest {
data class Test(val a: String, val b: Int)
@org.junit.Test
fun testPush() {
println(UpdateBuilder {
Test::a push Test("a", 1)
Test::b set 1
})
}
}