lucene4s alternatives and similar packages
Based on the "Database" category.
Alternatively, view lucene4s alternatives based on common mentions on social networks and blogs.
-
Slick
Slick (Scala Language Integrated Connection Kit) is a modern database query and access library for Scala -
Elastic4s
Elasticsearch Scala Client - Reactive, Non Blocking, Type Safe, HTTP Client -
PostgreSQL and MySQL async
Async database drivers to talk to PostgreSQL and MySQL in Scala. -
ScalikeJDBC
A tidy SQL-based DB access library for Scala developers. This library naturally wraps JDBC APIs and provides you easy-to-use APIs. -
scala-redis
A scala library for connecting to a redis server, or a cluster of redis nodes using consistent hashing on the client side. -
Phantom
Schema safe, type-safe, reactive Scala driver for Cassandra/Datastax Enterprise -
ReactiveMongo
:leaves: Non-blocking, Reactive MongoDB Driver for Scala -
rediscala
Non-blocking, Reactive Redis driver for Scala (with Sentinel support) -
Squeryl
A Scala DSL for talking with databases with minimum verbosity and maximum type safety -
#<Sawyer::Resource:0x00007f161059a678>
Strong type constraints for Scala -
SwayDB
Persistent and in-memory key-value storage engine for JVM that scales on a single machine. -
Pulsar4s
Idiomatic, typesafe, and reactive Scala client for Apache Pulsar -
scredis
Non-blocking, ultra-fast Scala Redis client built on top of Akka IO, used in production at Livestream -
Scala-Forklift
Type-safe data migration tool for Slick, Git and beyond. -
AnormCypher
Neo4j Scala library based on Anorm in the Play Framework -
Scruid
Scala + Druid: Scruid. A library that allows you to compose queries in Scala, and parse the result back into typesafe classes. -
Clickhouse-scala-client
Clickhouse Scala Client with Reactive Streams support -
Tepkin
Reactive MongoDB Driver for Scala built on top of Akka IO and Akka Streams. -
Couchbase
The Couchbase Monorepo for JVM Clients: Java, Scala, io-core… -
ScalaRelational
Type-Safe framework for defining, modifying, and querying SQL databases -
ReactiveNeo
[DISCONTINUED] Reactive type-safe Scala driver for Neo4J -
GCP Datastore Akka Persistence Plugin
akka-persistence-gcp-datastore is a journal and snapshot store plugin for akka-persistence using google cloud firestore in datastore mode.
WorkOS - The modern identity platform for B2B SaaS
Do you think we are missing an alternative of lucene4s or a related project?
README
lucene4s
Light-weight convenience wrapper around Lucene to simplify complex tasks and add Scala sugar.
Setup
lucene4s is published to Sonatype OSS and Maven Central currently supporting Scala 2.11, 2.12, 2.13, and 3.0.
Configuring the dependency in SBT simply requires:
libraryDependencies += "com.outr" %% "lucene4s" % "1.11.1"
Using
Imports
You may find yourself needing other imports depending on what you're doing, but the majority of functionality can be
achieved simply importing com.outr.lucene4s._
:
import com.outr.lucene4s._
Creating a Lucene Instance
Lucene
is the object utilized for doing anything with Lucene, so you first need to instantiate it:
val directory = Paths.get("index")
val lucene = new DirectLucene(Nil, directory = Option(directory))
NOTE: If you leave directory
blank or set it to None (the default) it will use an in-memory index.
Creating Fields
For type-safety and convenience we can create the fields we'll be using in the document ahead of time:
val name = lucene.create.field[String]("name")
val address = lucene.create.field[String]("address")
Inserting Documents
Inserting is quite easy using the document builder:
lucene.doc().fields(name("John Doe"), address("123 Somewhere Rd.")).index()
lucene.doc().fields(name("Jane Doe"), address("123 Somewhere Rd.")).index()
Querying Documents
Querying documents is just as easy with the query builder:
val paged = lucene.query().sort(Sort(name)).search()
paged.results.foreach { searchResult =>
println(s"Name: ${searchResult(name)}, Address: ${searchResult(address)}")
}
This will return a PagedResults
instance with the page size set to the limit
. There are convenience methods for
navigating the pagination and accessing the results.
The above code will output:
Name: John Doe, Address: 123 Somewhere Rd.
Name: Jane Doe, Address: 123 Somewhere Rd.
Highlighting Results
Though querying is nice, we may want to stylize the output to show the matched results. This is pretty simple:
val paged = lucene.query().sort(Sort(name)).filter(fuzzy(name("jhn"))).highlight().search()
paged.results.foreach { searchResult =>
val highlighting = searchResult.highlighting(name).head
println(s"Fragment: ${highlighting.fragment}, Word: ${highlighting.word}")
}
The above code will output:
Fragment: <em>John</em> Doe, Word: John
Fragment: <em>Jane</em> Doe, Word: Jane
Faceted Searching
See https://github.com/outr/lucene4s/blob/master/implementation/src/test/scala/tests/FacetsSpec.scala
Full-Text Searching
In lucene4s the Lucene
instance holds a fullText
Field
that contains a concatenation of all the fields that
are configured as fullTextSearchable
. This defaults to Lucene.defaultFullTextSearchable
which defaults to false.
The fullText
field is the default field used for searches if it's not specified in the SearchTerm
. Let's see an example:
val paged = lucene.query().filter(wildcard("doe*")).search()
paged.total should be(4)
paged.results(0)(firstName) should be("John")
paged.results(1)(firstName) should be("Jane")
paged.results(2)(firstName) should be("Baby")
paged.results(3)(firstName) should be("James")
For a complete example, see: https://github.com/outr/lucene4s/blob/master/implementation/src/test/scala/tests/FullTextSpec.scala
Keyword Searching
As we saw previously, the fullText
field provides us with a concatenation of all fields configured to be fullTextSearchable
.
In addition, if you create an instance of KeywordIndexing
you can query against a no-duplicates index of keywords for
the fullText
(although you can override defaults to apply keyword indexing to any field). All we have to do is create
and instance referencing the Lucene
instance and the name (used for storage purposes):
val keywordIndexing = KeywordIndexing(lucene, "keywords")
val keywords = keywordIndexing.search("do*")
println("Keywords: ${keywords.results.map(_.word).mkString(", ")}")
The above code would output:
Keywords: Doe
For the complete example see: https://github.com/outr/lucene4s/blob/master/implementation/src/test/scala/tests/SimpleSpec.scala
Case Class Support
lucene4s provides a powerful Macro-based system to generate two-way mappings between case classes and Lucene fields at
compile-time. This is accomplished through the use of Searchable
. The setup is pretty simple.
Setup
First we need to define a case class to model the data in the index:
case class Person(id: Int, firstName: String, lastName: String, age: Int, address: String, city: String, state: String, zip: String)
As you can see, this is a bare-bones case class with nothing special about it.
Next we need to define a Searchable
trait the defines the unique identification for update and delete:
trait SearchablePerson extends Searchable[Person] {
// This is necessary for update and delete to reference the correct document.
override def idSearchTerms(person: Person): List[SearchTerm] = List(exact(id(person.id)))
/*
Though at compile-time all fields will be generated from the params in `Person`, for code-completion we can define
an unimplemented method in order to properly reference the field. This will still compile without this definition,
but most IDEs will complain.
*/
def id: Field[Int]
}
As the last part of our set up we simply need to generate it from our Lucene
instance:
val people = lucene.create.searchable[SearchablePerson]
Inserting
Now that we've configured everything inserting a person is trivial:
people.insert(Person(1, "John", "Doe", 23, "123 Somewhere Rd.", "Lalaland", "California", "12345")).index()
Notice that we still have to call index()
at the end for it to actually invoke. This allows us to do more advanced
tasks like adding facets, adding non-Searchable fields, etc. before actually inserting.
Updating
Now lets try updating our Person
:
people.update(Person(1, "John", "Doe", 23, "321 Nowhere St.", "Lalaland", "California", "12345")).index()
As you can see here, the signature is quite similar to insert
. Internally this will utilize idSearchTerms
as we
declared previously to apply the update. In this case that means as long as we don't change the id (1) then calls to
update will replace an existing record if one exists.
Querying
Querying works very much the same as in the previous examples, except we get our QueryBuilder
from our people
instance:
val paged = people.query().search()
paged.entries.foreach { person =>
println(s"Person: $person")
}
Note that instead of calling paged.results
we call paged.entries
as it represents the conversion to Person
. We can
still use paged.results
if we want access to the SearchResult
like before.
Deleting
Deleting is just as easy as inserting and updating:
people.delete(Person(1, "John", "Doe", 23, "321 Nowhere St.", "Lalaland", "California", "12345"))
Additional Information
All Searchable
implementations automatically define a docType
field that is used to uniquely separate different
Searchable
instances so you don't have to worry about multiple different instances overlapping.
For more examples see https://github.com/outr/lucene4s/blob/master/implementation/src/test/scala/tests/SearchableSpec.scala
Geospatial Support
One of the great features of Lucene is geospatial querying and what Lucene wrapper would be complete without it?
Creating a Spatial Field
In order to create a stored, queryable, filterable, and sortable latitude and longitude you need only create a
SpatialPoint
field:
val location: Field[SpatialPoint] = lucene.create.field[SpatialPoint]("location")
Sorting Nearest a Point
Most of the time it's most useful to take an existing latitude and longitude and sort your results returning the nearest documents to that location:
val paged = lucene.query().sort(Sort.nearest(location, SpatialPoint(40.7142, -74.0119))).search()
Filtering by Distance
If you want to filter your results to only include entries within a certain range of a location:
val newYorkCity = SpatialPoint(40.7142, -74.0119)
val paged = lucene
.query()
.sort(Sort.nearest(location, newYorkCity))
.filter(spatialDistance(location, newYorkCity, 50.miles))
.search()