Skip to content

Add extension methods for Indexes #1532

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 17, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config/spotbugs/exclude.xml
Original file line number Diff line number Diff line change
@@ -235,6 +235,12 @@
<Method name="~include|exclude"/>
<Bug pattern="BC_BAD_CAST_TO_ABSTRACT_COLLECTION"/>
</Match>
<Match>
<!-- MongoDB status: "False Positive", SpotBugs rank: 17 -->
<Class name="com.mongodb.kotlin.client.model.Indexes"/>
<Method name="~ascending|descending|geo2dsphere"/>
<Bug pattern="BC_BAD_CAST_TO_ABSTRACT_COLLECTION"/>
</Match>

<!-- Spotbugs reports false positives for suspendable operations with default params
see: https://github.com/Kotlin/kotlinx.coroutines/issues/3099
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* Copyright 2008-present MongoDB, Inc.
* Copyright (C) 2016/2022 Litote
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @custom-license-header
*/
package com.mongodb.kotlin.client.model

import com.mongodb.client.model.Indexes
import kotlin.reflect.KProperty
import org.bson.conversions.Bson

/**
* Indexes extension methods to improve Kotlin interop
*
* @since 5.3
*/
public object Indexes {
/**
* Create an index key for an ascending index on the given fields.
*
* @param properties the properties, which must contain at least one
* @return the index specification @mongodb.driver.manual core/indexes indexes
*/
public fun ascending(vararg properties: KProperty<*>): Bson = Indexes.ascending(properties.map { it.path() })

/**
* Create an index key for an ascending index on the given fields.
*
* @param properties the properties, which must contain at least one
* @return the index specification @mongodb.driver.manual core/indexes indexes
*/
public fun ascending(properties: Iterable<KProperty<*>>): Bson = Indexes.ascending(properties.map { it.path() })

/**
* Create an index key for a descending index on the given fields.
*
* @param properties the properties, which must contain at least one
* @return the index specification @mongodb.driver.manual core/indexes indexes
*/
public fun descending(vararg properties: KProperty<*>): Bson = Indexes.descending(properties.map { it.path() })

/**
* Create an index key for a descending index on the given fields.
*
* @param properties the properties, which must contain at least one
* @return the index specification @mongodb.driver.manual core/indexes indexes
*/
public fun descending(properties: Iterable<KProperty<*>>): Bson = Indexes.descending(properties.map { it.path() })

/**
* Create an index key for an 2dsphere index on the given fields.
*
* @param properties the properties, which must contain at least one
* @return the index specification @mongodb.driver.manual core/2dsphere 2dsphere Index
*/
public fun geo2dsphere(vararg properties: KProperty<*>): Bson = Indexes.geo2dsphere(properties.map { it.path() })

/**
* Create an index key for an 2dsphere index on the given fields.
*
* @param properties the properties, which must contain at least one
* @return the index specification @mongodb.driver.manual core/2dsphere 2dsphere Index
*/
public fun geo2dsphere(properties: Iterable<KProperty<*>>): Bson = Indexes.geo2dsphere(properties.map { it.path() })

/**
* Create an index key for a text index on the given property.
*
* @param property the property to create a text index on
* @return the index specification @mongodb.driver.manual core/text text index
*/
public fun <T> text(property: KProperty<T>): Bson = Indexes.text(property.path())

/**
* Create an index key for a hashed index on the given property.
*
* @param property the property to create a hashed index on
* @return the index specification @mongodb.driver.manual core/hashed hashed index
*/
public fun <T> hashed(property: KProperty<T>): Bson = Indexes.hashed(property.path())

/**
* Create an index key for a 2d index on the given field.
*
* <p>
* <strong>Note: </strong>A 2d index is for data stored as points on a two-dimensional plane. The 2d index is
* intended for legacy coordinate pairs used in MongoDB 2.2 and earlier. </p>
*
* @param property the property to create a 2d index on
* @return the index specification @mongodb.driver.manual core/2d 2d index
*/
public fun <T> geo2d(property: KProperty<T>): Bson = Indexes.geo2d(property.path())
}
Original file line number Diff line number Diff line change
@@ -48,15 +48,24 @@ class ExtensionsApiTest {
assertTrue(notImplemented.isEmpty(), "Some possible Updates were not implemented: $notImplemented")
}

@Test
fun shouldHaveAllIndexesExtensions() {
val kotlinExtensions: Set<String> = getKotlinExtensions("Indexes")
val javaMethods: Set<String> = getJavaMethods("Indexes")
val notImplemented = javaMethods subtract kotlinExtensions
assertTrue(notImplemented.isEmpty(), "Some possible Indexes were not implemented: $notImplemented")
}

private fun getKotlinExtensions(className: String): Set<String> {
return ClassGraph()
.enableClassInfo()
.enableMethodInfo()
.acceptPackages("com.mongodb.kotlin.client.model")
.scan()
.use {
it.allClasses
.filter { it.simpleName == "${className}" }
.use { result ->
result.allClasses
.filter { it.simpleName == className }
.asSequence()
.flatMap { it.methodInfo }
.filter { it.isPublic }
.map { it.name }
@@ -69,10 +78,14 @@ class ExtensionsApiTest {
return ClassGraph().enableClassInfo().enableMethodInfo().acceptPackages("com.mongodb.client.model").scan().use {
it.getClassInfo("com.mongodb.client.model.$className")
.methodInfo
.filter {
it.isPublic &&
it.parameterInfo.isNotEmpty() &&
it.parameterInfo[0].typeDescriptor.toStringWithSimpleNames().equals("String")
.filter { methodInfo ->
methodInfo.isPublic &&
methodInfo.parameterInfo.isNotEmpty() &&
methodInfo.parameterInfo[0]
.typeDescriptor
.toStringWithSimpleNames()
.equals("String") // only method starting
// with a String (property name)
}
.map { m -> m.name }
.toSet()
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright 2008-present MongoDB, Inc.
* Copyright (C) 2016/2022 Litote
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @custom-license-header
*/
package com.mongodb.kotlin.client.model

import com.mongodb.client.model.Indexes
import com.mongodb.client.model.Indexes.compoundIndex
import com.mongodb.kotlin.client.model.Indexes.ascending
import com.mongodb.kotlin.client.model.Indexes.descending
import com.mongodb.kotlin.client.model.Indexes.geo2d
import com.mongodb.kotlin.client.model.Indexes.geo2dsphere
import com.mongodb.kotlin.client.model.Indexes.hashed
import com.mongodb.kotlin.client.model.Indexes.text
import kotlin.test.assertEquals
import org.bson.BsonDocument
import org.bson.conversions.Bson
import org.junit.Test

class IndexesTest {

@Test
fun `ascending index`() {
assertEquals(""" {name: 1} """, ascending(Person::name))
assertEquals(""" {name: 1, age: 1} """, ascending(Person::name, Person::age))
assertEquals(""" {name: 1, age: 1} """, ascending(listOf(Person::name, Person::age)))
}

@Test
fun `descending index`() {
assertEquals(""" {name: -1} """, descending(Person::name))
assertEquals(""" {name: -1, age: -1} """, descending(Person::name, Person::age))
assertEquals(""" {name: -1, age: -1} """, descending(listOf(Person::name, Person::age)))
}

@Test
fun `geo2dsphere index`() {
assertEquals(""" {name: "2dsphere"} """, geo2dsphere(Person::name))
assertEquals(""" {name: "2dsphere", age: "2dsphere"} """, geo2dsphere(Person::name, Person::age))
assertEquals(""" {name: "2dsphere", age: "2dsphere"} """, geo2dsphere(listOf(Person::name, Person::age)))
}

@Test
fun `geo2d index`() {
assertEquals(""" {name: "2d"} """, geo2d(Person::name))
}

@Test
fun `text helper`() {
assertEquals(""" {name: "text"} """, text(Person::name))
assertEquals(""" { "${'$'}**" : "text"} """, Indexes.text())
}

@Test
fun `hashed index`() {
assertEquals(""" {name: "hashed"} """, hashed(Person::name))
}

@Test
fun `compound index`() {
assertEquals(""" {name : 1, age : -1} """, compoundIndex(ascending(Person::name), descending(Person::age)))
}

@Test
fun `should test equals on CompoundIndex`() {
assertEquals(
compoundIndex(ascending(Person::name), descending(Person::age)),
compoundIndex(ascending(Person::name), descending(Person::age)))

assertEquals(
compoundIndex(listOf(ascending(Person::name), descending(Person::age))),
compoundIndex(listOf(ascending(Person::name), descending(Person::age))))
}

// Utils
private data class Person(val name: String, val age: Int)

private fun assertEquals(expected: String, result: Bson) =
assertEquals(BsonDocument.parse(expected), result.toBsonDocument())
}