Skip to content

Commit aafeb9b

Browse files
author
denis_savitsky
committed
Add support for $pop
1 parent 0ce9ed8 commit aafeb9b

File tree

8 files changed

+117
-1
lines changed

8 files changed

+117
-1
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,17 @@ val q = update[Student](_.addToSetAll(_.courses, List(42, 44, 53)))
349349
// q us {"$addToSet": {"courses": {$each: [42, 44, 53] }}}
350350
```
351351

352+
2. $pop
353+
354+
```scala
355+
case class Student(id: Int, courses: List[Int])
356+
357+
val q = update[Student](_.popHead(_.courses)) // removes the first element
358+
// q us {"$pop": {"courses": -1}}
359+
360+
val q1 = update[Student](_.popLast(_.courses)) // removes the last element
361+
// q1 us {"$pop": {"courses": 1}}
362+
```
352363

353364
#### Projection
354365

oolong-core/src/main/scala/oolong/AstParser.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,14 @@ private[oolong] class DefaultAstParser(using quotes: Quotes) extends AstParser {
313313
val prop = parsePropSelector(selectProp)
314314
val value = getValueOrIterable(valueExpr)
315315
parseUpdater(updater, FieldUpdateExpr.AddToSet(UExpr.Prop(prop), value, multipleValues = false) :: acc)
316+
317+
case '{($updater: Updater[Doc]).popHead($selectProp)} =>
318+
val prop = parsePropSelector(selectProp)
319+
parseUpdater(updater, FieldUpdateExpr.Pop(UExpr.Prop(prop), FieldUpdateExpr.Pop.Remove.First) :: acc)
320+
321+
case '{($updater: Updater[Doc]).popLast($selectProp)} =>
322+
val prop = parsePropSelector(selectProp)
323+
parseUpdater(updater, FieldUpdateExpr.Pop(UExpr.Prop(prop), FieldUpdateExpr.Pop.Remove.Last) :: acc)
316324

317325
case '{ $updater: Updater[Doc] } =>
318326
updater match {

oolong-core/src/main/scala/oolong/UExpr.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ private[oolong] object UExpr {
4343
// array update operators
4444
case class AddToSet(prop: Prop, expr: UExpr, multipleValues: Boolean) extends FieldUpdateExpr(prop)
4545

46+
case class Pop(prop: Prop, remove: Pop.Remove) extends FieldUpdateExpr(prop)
47+
48+
object Pop {
49+
enum Remove {
50+
case First, Last
51+
}
52+
}
53+
4654
}
4755

4856
}

oolong-core/src/main/scala/oolong/dsl/Dsl.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,7 @@ sealed trait Updater[DocT] {
5555
def addToSetAll[PropT, ValueT](selectProp: DocT => Iterable[PropT], value: Iterable[ValueT])(using
5656
PropT =:= ValueT
5757
): Updater[DocT] = useWithinMacro("addToSet")
58+
59+
def popHead(selectProp: DocT => Iterable[?]): Updater[DocT] = useWithinMacro("popHead")
60+
def popLast(selectProp: DocT => Iterable[?]): Updater[DocT] = useWithinMacro("popLast")
5861
}

oolong-mongo-it/src/test/scala/oolong/mongo/OolongMongoUpdateSpec.scala

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class OolongMongoUpdateSpec extends AsyncFlatSpec with ForAllTestContainer with
3232
TestClass("3", 12, InnerClass("sdf", 1), Nil, None, List.empty),
3333
TestClass("4", 13, InnerClass("sdf", 1), List(1), None, List.empty),
3434
TestClass("12345", 12, InnerClass("sdf", 11), Nil, None, List.empty),
35+
TestClass("popHead", 1, InnerClass("popHead", 1), List(1, 2, 3), None, List.empty),
36+
TestClass("popTail", 1, InnerClass("popTail", 1), List(1, 2, 3), None, List.empty)
3537
)
3638

3739
implicit val ec = ExecutionContext.global
@@ -168,4 +170,38 @@ class OolongMongoUpdateSpec extends AsyncFlatSpec with ForAllTestContainer with
168170
)
169171
}
170172

173+
it should "$pop the fist element" in {
174+
for {
175+
upd <- collection
176+
.findOneAndUpdate(
177+
query[TestClass](_.field1 == "popHead"),
178+
update[TestClass](
179+
_.popHead(_.field4)
180+
),
181+
new FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER)
182+
)
183+
.head()
184+
.map(BsonDecoder[TestClass].fromBson(_).get)
185+
} yield assert(
186+
upd.field4 == List(2, 3)
187+
)
188+
}
189+
190+
it should "$pop the last element" in {
191+
for {
192+
upd <- collection
193+
.findOneAndUpdate(
194+
query[TestClass](_.field1 == "popTail"),
195+
update[TestClass](
196+
_.popLast(_.field4)
197+
),
198+
new FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER)
199+
)
200+
.head()
201+
.map(BsonDecoder[TestClass].fromBson(_).get)
202+
} yield assert(
203+
upd.field4 == List(1, 2)
204+
)
205+
}
206+
171207
}

oolong-mongo/src/main/scala/oolong/mongo/MongoUpdateCompiler.scala

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import oolong.*
88
import oolong.UExpr.FieldUpdateExpr
99
import oolong.bson.meta.QueryMeta
1010
import oolong.mongo.MongoUpdateNode.MongoUpdateOp
11+
import oolong.mongo.MongoUpdateNode.MongoUpdateOp.Pop.Remove
1112
import oolong.mongo.MongoUpdateNode as MU
1213
import org.mongodb.scala.bson.BsonArray
1314
import org.mongodb.scala.bson.BsonBoolean
@@ -48,6 +49,11 @@ object MongoUpdateCompiler extends Backend[UExpr, MU, BsonDocument] {
4849
case FieldUpdateExpr.Unset(prop) => MU.MongoUpdateOp.Unset(MU.Prop(renames.getOrElse(prop.path, prop.path)))
4950
case FieldUpdateExpr.AddToSet(prop, expr, each) =>
5051
MU.MongoUpdateOp.AddToSet(MU.Prop(renames.getOrElse(prop.path, prop.path)), rec(expr), each)
52+
case FieldUpdateExpr.Pop(prop, remove) =>
53+
val muRemove = remove match
54+
case FieldUpdateExpr.Pop.Remove.First => Remove.First
55+
case FieldUpdateExpr.Pop.Remove.Last => Remove.Last
56+
MU.MongoUpdateOp.Pop(MU.Prop(renames.getOrElse(prop.path, prop.path)), muRemove)
5157
})
5258
case UExpr.ScalaCode(code) => MU.ScalaCode(code)
5359
case UExpr.Constant(t) => MU.Constant(t)
@@ -91,7 +97,10 @@ object MongoUpdateCompiler extends Backend[UExpr, MU, BsonDocument] {
9197
)("$setOnInsert"),
9298
renderOps(
9399
ops.collect { case s: MU.MongoUpdateOp.AddToSet => s }.map(renderAddToSet)
94-
)("$addToSet")
100+
)("$addToSet"),
101+
renderOps(
102+
ops.collect { case s: MU.MongoUpdateOp.Pop => s }.map(op => render(op.prop) + ": " + render(op.value))
103+
)("$pop")
95104
).flatten
96105
.mkString("{\n", ",\n", "\n}")
97106

@@ -153,6 +162,7 @@ object MongoUpdateCompiler extends Backend[UExpr, MU, BsonDocument] {
153162
val tRenames = targetOps(ops.collect { case s: MU.MongoUpdateOp.Rename => s })
154163
val tSetOnInserts = targetOps(ops.collect { case s: MU.MongoUpdateOp.SetOnInsert => s })
155164
val tAddToSets = targetOps(ops.collect { case s: MU.MongoUpdateOp.AddToSet => s })
165+
val tPops = targetOps(ops.collect { case s: MU.MongoUpdateOp.Pop => s })
156166

157167
// format: off
158168
def updaterGroup(groupName: String, updaters: List[Expr[(String, BsonValue)]]): Option[Expr[(String, BsonDocument)]] =
@@ -173,6 +183,7 @@ object MongoUpdateCompiler extends Backend[UExpr, MU, BsonDocument] {
173183
updaterGroup("$rename", tRenames),
174184
updaterGroup("$setOnInsert", tSetOnInserts),
175185
updaterGroup("$addToSet", tAddToSets),
186+
updaterGroup("$pop", tPops),
176187
).flatten
177188

178189
'{

oolong-mongo/src/main/scala/oolong/mongo/MongoUpdateNode.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,16 @@ case object MongoUpdateNode {
3232

3333
case class AddToSet(override val prop: Prop, override val value: MU, each: Boolean)
3434
extends MongoUpdateOp(prop, value)
35+
36+
case class Pop(override val prop: Prop, remove: Pop.Remove) extends MongoUpdateOp(prop, remove.toConstant)
37+
object Pop {
38+
enum Remove {
39+
case First, Last
40+
41+
def toConstant: MU.Constant[Int] = this match
42+
case Remove.First => MU.Constant(-1)
43+
case Remove.Last => MU.Constant(1)
44+
}
45+
}
3546
}
3647
}

oolong-mongo/src/test/scala/oolong/mongo/UpdateSpec.scala

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,34 @@ class UpdateSpec extends AnyFunSuite {
218218
)
219219
}
220220

221+
test("$pop first") {
222+
val q = update[TestClass](_.popHead(_.nestedListField))
223+
val repr = renderUpdate[TestClass](_.popHead(_.nestedListField))
224+
test(
225+
q,
226+
repr,
227+
BsonDocument(
228+
"$pop" -> BsonDocument(
229+
"nestedListField" -> BsonInt32(-1)
230+
)
231+
),
232+
)
233+
}
234+
235+
test("$pop last") {
236+
val q = update[TestClass](_.popLast(_.nestedListField))
237+
val repr = renderUpdate[TestClass](_.popLast(_.nestedListField))
238+
test(
239+
q,
240+
repr,
241+
BsonDocument(
242+
"$pop" -> BsonDocument(
243+
"nestedListField" -> BsonInt32(1)
244+
)
245+
),
246+
)
247+
}
248+
221249
test("several update operators combined") {
222250
val q = update[TestClass](
223251
_.unset(_.dateField)

0 commit comments

Comments
 (0)