Reorg modules; complete impls/tests

This commit is contained in:
2025-03-25 07:20:30 -04:00
parent 815f506339
commit 50188d939e
363 changed files with 11626 additions and 1574 deletions

94
src/scala/pom.xml Normal file
View File

@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>solutions.bitbadger</groupId>
<artifactId>documents</artifactId>
<version>4.0.0-alpha1-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<groupId>solutions.bitbadger.documents</groupId>
<artifactId>scala</artifactId>
<name>${project.groupId}:${project.artifactId}</name>
<description>Expose a document store interface for PostgreSQL and SQLite (Scala Library)</description>
<url>https://bitbadger.solutions/open-source/relational-documents/jvm/</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.code.style>official</kotlin.code.style>
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
</properties>
<build>
<sourceDirectory>${project.basedir}/src/main/scala</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/scala</testSourceDirectory>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>4.9.2</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire.version}</version>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${failsafe.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>solutions.bitbadger.documents</groupId>
<artifactId>core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala3-library_3</artifactId>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

9
src/scala/scala.iml Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$" dumb="true">
<sourceFolder url="file://$MODULE_DIR$/src/main/scala" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/test/scala" isTestSource="true" />
</content>
</component>
</module>

View File

@@ -0,0 +1,105 @@
package solutions.bitbadger.documents.scala
import solutions.bitbadger.documents.{Field, FieldMatch}
import solutions.bitbadger.documents.java.Count as CoreCount
import java.sql.Connection
import _root_.scala.jdk.CollectionConverters.*
/**
* Functions to count documents
*/
object Count:
/**
* Count all documents in the table
*
* @param tableName The name of the table in which documents should be counted
* @param conn The connection over which documents should be counted
* @return A count of the documents in the table
* @throws DocumentException If any dependent process does
*/
def all(tableName: String, conn: Connection): Long =
CoreCount.all(tableName, conn)
/**
* Count all documents in the table (creates connection)
*
* @param tableName The name of the table in which documents should be counted
* @return A count of the documents in the table
* @throws DocumentException If no connection string has been set
*/
def all(tableName: String): Long =
CoreCount.all(tableName)
/**
* Count documents using a field comparison
*
* @param tableName The name of the table in which documents should be counted
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched (optional, default `ALL`)
* @param conn The connection on which the deletion should be executed
* @return A count of the matching documents in the table
* @throws DocumentException If no dialect has been configured
*/
def byFields(tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch], conn: Connection): Long =
CoreCount.byFields(tableName, fields.asJava, howMatched.orNull, conn)
/**
* Count documents using a field comparison (creates connection)
*
* @param tableName The name of the table in which documents should be counted
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
* @return A count of the matching documents in the table
* @throws DocumentException If no connection string has been set
*/
def byFields(tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch] = None): Long =
CoreCount.byFields(tableName, fields.asJava, howMatched.orNull)
/**
* Count documents using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which documents should be counted
* @param criteria The object for which JSON containment should be checked
* @param conn The connection on which the count should be executed
* @return A count of the matching documents in the table
* @throws DocumentException If called on a SQLite connection
*/
def byContains[TContains](tableName: String, criteria: TContains, conn: Connection): Long =
CoreCount.byContains(tableName, criteria, conn)
/**
* Count documents using a JSON containment query (PostgreSQL only; creates connection)
*
* @param tableName The name of the table in which documents should be counted
* @param criteria The object for which JSON containment should be checked
* @return A count of the matching documents in the table
* @throws DocumentException If called on a SQLite connection
*/
def byContains[TContains](tableName: String, criteria: TContains): Long =
CoreCount.byContains(tableName, criteria)
/**
* Count documents using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table in which documents should be counted
* @param path The JSON path comparison to match
* @param conn The connection on which the count should be executed
* @return A count of the matching documents in the table
* @throws DocumentException If called on a SQLite connection
*/
def byJsonPath(tableName: String, path: String, conn: Connection): Long =
CoreCount.byJsonPath(tableName, path, conn)
/**
* Count documents using a JSON Path match query (PostgreSQL only; creates connection)
*
* @param tableName The name of the table in which documents should be counted
* @param path The JSON path comparison to match
* @return A count of the matching documents in the table
* @throws DocumentException If called on a SQLite connection
*/
def byJsonPath(tableName: String, path: String): Long =
CoreCount.byJsonPath(tableName, path)

View File

@@ -0,0 +1,210 @@
package solutions.bitbadger.documents.scala
import solutions.bitbadger.documents.{Configuration, Parameter}
import java.sql.{Connection, ResultSet}
import scala.reflect.ClassTag
import scala.util.Using
object Custom:
/**
* Execute a query that returns a list of results
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
* @param conn The connection over which the query should be executed
* @param mapFunc The mapping function between the document and the domain item
* @return A list of results for the given query
* @throws DocumentException If parameters are invalid
*/
def list[TDoc](query: String, parameters: Seq[Parameter[?]], conn: Connection,
mapFunc: (ResultSet, ClassTag[TDoc]) => TDoc)(implicit tag: ClassTag[TDoc]): List[TDoc] =
Using(Parameters.apply(conn, query, parameters)) { stmt => Results.toCustomList[TDoc](stmt, mapFunc) }.get
/**
* Execute a query that returns a list of results
*
* @param query The query to retrieve the results
* @param conn The connection over which the query should be executed
* @param mapFunc The mapping function between the document and the domain item
* @return A list of results for the given query
* @throws DocumentException If parameters are invalid
*/
def list[TDoc](query: String, conn: Connection,
mapFunc: (ResultSet, ClassTag[TDoc]) => TDoc)(implicit tag: ClassTag[TDoc]): List[TDoc] =
list(query, List(), conn, mapFunc)
/**
* Execute a query that returns a list of results (creates connection)
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
* @param mapFunc The mapping function between the document and the domain item
* @return A list of results for the given query
* @throws DocumentException If parameters are invalid
*/
def list[TDoc](query: String, parameters: Seq[Parameter[?]],
mapFunc: (ResultSet, ClassTag[TDoc]) => TDoc)(implicit tag: ClassTag[TDoc]): List[TDoc] =
Using(Configuration.dbConn()) { conn => list[TDoc](query, parameters, conn, mapFunc) }.get
/**
* Execute a query that returns a list of results (creates connection)
*
* @param query The query to retrieve the results
* @param mapFunc The mapping function between the document and the domain item
* @return A list of results for the given query
* @throws DocumentException If parameters are invalid
*/
def list[TDoc](query: String,
mapFunc: (ResultSet, ClassTag[TDoc]) => TDoc)(implicit tag: ClassTag[TDoc]): List[TDoc] =
list(query, List(), mapFunc)
/**
* Execute a query that returns one or no results
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
* @param conn The connection over which the query should be executed
* @param mapFunc The mapping function between the document and the domain item
* @return An `Option` value, with the document if one matches the query
* @throws DocumentException If parameters are invalid
*/
def single[TDoc](query: String, parameters: Seq[Parameter[?]], conn: Connection,
mapFunc: (ResultSet, ClassTag[TDoc]) => TDoc)(implicit tag: ClassTag[TDoc]): Option[TDoc] =
list[TDoc](s"$query LIMIT 1", parameters, conn, mapFunc).headOption
/**
* Execute a query that returns one or no results
*
* @param query The query to retrieve the results
* @param conn The connection over which the query should be executed
* @param mapFunc The mapping function between the document and the domain item
* @return An `Option` value, with the document if one matches the query
* @throws DocumentException If parameters are invalid
*/
def single[TDoc](query: String, conn: Connection,
mapFunc: (ResultSet, ClassTag[TDoc]) => TDoc)(implicit tag: ClassTag[TDoc]): Option[TDoc] =
list[TDoc](s"$query LIMIT 1", List(), conn, mapFunc).headOption
/**
* Execute a query that returns one or no results (creates connection)
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
* @param mapFunc The mapping function between the document and the domain item
* @return An `Option` value, with the document if one matches the query
* @throws DocumentException If parameters are invalid
*/
def single[TDoc](query: String, parameters: Seq[Parameter[?]],
mapFunc: (ResultSet, ClassTag[TDoc]) => TDoc)(implicit tag: ClassTag[TDoc]): Option[TDoc] =
Using(Configuration.dbConn()) { conn => single[TDoc](query, parameters, conn, mapFunc) }.get
/**
* Execute a query that returns one or no results (creates connection)
*
* @param query The query to retrieve the results
* @param mapFunc The mapping function between the document and the domain item
* @return An `Option` value, with the document if one matches the query
* @throws DocumentException If parameters are invalid
*/
def single[TDoc](query: String,
mapFunc: (ResultSet, ClassTag[TDoc]) => TDoc)(implicit tag: ClassTag[TDoc]): Option[TDoc] =
single[TDoc](query, List(), mapFunc)
/**
* Execute a query that returns no results
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
* @param conn The connection over which the query should be executed
* @throws DocumentException If parameters are invalid
*/
def nonQuery(query: String, parameters: Seq[Parameter[?]], conn: Connection): Unit =
Using(Parameters.apply(conn, query, parameters)) { stmt => stmt.executeUpdate() }
/**
* Execute a query that returns no results
*
* @param query The query to retrieve the results
* @param conn The connection over which the query should be executed
* @throws DocumentException If parameters are invalid
*/
def nonQuery(query: String, conn: Connection): Unit =
nonQuery(query, List(), conn)
/**
* Execute a query that returns no results (creates connection)
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
* @throws DocumentException If parameters are invalid
*/
def nonQuery(query: String, parameters: Seq[Parameter[?]]): Unit =
Using(Configuration.dbConn()) { conn => nonQuery(query, parameters, conn) }
/**
* Execute a query that returns no results (creates connection)
*
* @param query The query to retrieve the results
* @throws DocumentException If parameters are invalid
*/
def nonQuery(query: String): Unit =
nonQuery(query, List())
/**
* Execute a query that returns a scalar result
*
* @param query The query to retrieve the result
* @param parameters Parameters to use for the query
* @param conn The connection over which the query should be executed
* @param mapFunc The mapping function between the document and the domain item
* @return The scalar value from the query
* @throws DocumentException If parameters are invalid
*/
def scalar[T](query: String, parameters: Seq[Parameter[?]], conn: Connection,
mapFunc: (ResultSet, ClassTag[T]) => T)(implicit tag: ClassTag[T]): T =
Using(Parameters.apply(conn, query, parameters)) { stmt =>
Using(stmt.executeQuery()) { rs =>
rs.next()
mapFunc(rs, tag)
}.get
}.get
/**
* Execute a query that returns a scalar result
*
* @param query The query to retrieve the result
* @param conn The connection over which the query should be executed
* @param mapFunc The mapping function between the document and the domain item
* @return The scalar value from the query
* @throws DocumentException If parameters are invalid
*/
def scalar[T](query: String, conn: Connection,
mapFunc: (ResultSet, ClassTag[T]) => T)(implicit tag: ClassTag[T]): T =
scalar[T](query, List(), conn, mapFunc)
/**
* Execute a query that returns a scalar result (creates connection)
*
* @param query The query to retrieve the result
* @param parameters Parameters to use for the query
* @param mapFunc The mapping function between the document and the domain item
* @return The scalar value from the query
* @throws DocumentException If parameters are invalid
*/
def scalar[T](query: String, parameters: Seq[Parameter[?]],
mapFunc: (ResultSet, ClassTag[T]) => T)(implicit tag: ClassTag[T]): T =
Using(Configuration.dbConn()) { conn => scalar[T](query, parameters, conn, mapFunc) }.get
/**
* Execute a query that returns a scalar result (creates connection)
*
* @param query The query to retrieve the result
* @param mapFunc The mapping function between the document and the domain item
* @return The scalar value from the query
* @throws DocumentException If parameters are invalid
*/
def scalar[T](query: String,
mapFunc: (ResultSet, ClassTag[T]) => T)(implicit tag: ClassTag[T]): T =
scalar[T](query, List(), mapFunc)

View File

@@ -0,0 +1,72 @@
package solutions.bitbadger.documents.scala
import solutions.bitbadger.documents.DocumentIndex
import solutions.bitbadger.documents.java.Definition as CoreDefinition
import java.sql.Connection
import _root_.scala.jdk.CollectionConverters.*
object Definition:
/**
* Create a document table if necessary
*
* @param tableName The table whose existence should be ensured (may include schema)
* @param conn The connection on which the query should be executed
* @throws DocumentException If the dialect is not configured
*/
def ensureTable(tableName: String, conn: Connection): Unit =
CoreDefinition.ensureTable(tableName, conn)
/**
* Create a document table if necessary
*
* @param tableName The table whose existence should be ensured (may include schema)
* @throws DocumentException If no connection string has been set
*/
def ensureTable(tableName: String): Unit =
CoreDefinition.ensureTable(tableName)
/**
* Create an index on field(s) within documents in the specified table if necessary
*
* @param tableName The table to be indexed (may include schema)
* @param indexName The name of the index to create
* @param fields One or more fields to be indexed
* @param conn The connection on which the query should be executed
* @throws DocumentException If any dependent process does
*/
def ensureFieldIndex(tableName: String, indexName: String, fields: Seq[String], conn: Connection): Unit =
CoreDefinition.ensureFieldIndex(tableName, indexName, fields.asJava, conn)
/**
* Create an index on field(s) within documents in the specified table if necessary
*
* @param tableName The table to be indexed (may include schema)
* @param indexName The name of the index to create
* @param fields One or more fields to be indexed
* @throws DocumentException If no connection string has been set, or if any dependent process does
*/
def ensureFieldIndex(tableName: String, indexName: String, fields: Seq[String]): Unit =
CoreDefinition.ensureFieldIndex(tableName, indexName, fields.asJava)
/**
* Create a document index on a table (PostgreSQL only)
*
* @param tableName The table to be indexed (may include schema)
* @param indexType The type of index to ensure
* @param conn The connection on which the query should be executed
* @throws DocumentException If called on a SQLite connection
*/
def ensureDocumentIndex(tableName: String, indexType: DocumentIndex, conn: Connection): Unit =
CoreDefinition.ensureDocumentIndex(tableName, indexType, conn)
/**
* Create a document index on a table (PostgreSQL only)
*
* @param tableName The table to be indexed (may include schema)
* @param indexType The type of index to ensure
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def ensureDocumentIndex(tableName: String, indexType: DocumentIndex): Unit =
CoreDefinition.ensureDocumentIndex(tableName, indexType)

View File

@@ -0,0 +1,98 @@
package solutions.bitbadger.documents.scala
import solutions.bitbadger.documents.{Field, FieldMatch}
import solutions.bitbadger.documents.java.Delete as CoreDelete
import java.sql.Connection
import _root_.scala.jdk.CollectionConverters.*
/**
* Functions to delete documents
*/
object Delete:
/**
* Delete a document by its ID
*
* @param tableName The name of the table from which documents should be deleted
* @param docId The ID of the document to be deleted
* @param conn The connection on which the deletion should be executed
* @throws DocumentException If no dialect has been configured
*/
def byId[TKey](tableName: String, docId: TKey, conn: Connection): Unit =
CoreDelete.byId(tableName, docId, conn)
/**
* Delete a document by its ID (creates connection)
*
* @param tableName The name of the table from which documents should be deleted
* @param docId The ID of the document to be deleted
* @throws DocumentException If no connection string has been set
*/
def byId[TKey](tableName: String, docId: TKey): Unit =
CoreDelete.byId(tableName, docId)
/**
* Delete documents using a field comparison
*
* @param tableName The name of the table from which documents should be deleted
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
* @param conn The connection on which the deletion should be executed
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def byFields(tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch], conn: Connection): Unit =
CoreDelete.byFields(tableName, fields.asJava, howMatched.orNull, conn)
/**
* Delete documents using a field comparison (creates connection)
*
* @param tableName The name of the table from which documents should be deleted
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
* @throws DocumentException If no connection string has been set, or if parameters are invalid
*/
def byFields(tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch] = None): Unit =
CoreDelete.byFields(tableName, fields.asJava, howMatched.orNull)
/**
* Delete documents using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table from which documents should be deleted
* @param criteria The object for which JSON containment should be checked
* @param conn The connection on which the deletion should be executed
* @throws DocumentException If called on a SQLite connection
*/
def byContains[TContains](tableName: String, criteria: TContains, conn: Connection): Unit =
CoreDelete.byContains(tableName, criteria, conn)
/**
* Delete documents using a JSON containment query (PostgreSQL only; creates connection)
*
* @param tableName The name of the table from which documents should be deleted
* @param criteria The object for which JSON containment should be checked
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def byContains[TContains](tableName: String, criteria: TContains): Unit =
CoreDelete.byContains(tableName, criteria)
/**
* Delete documents using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table from which documents should be deleted
* @param path The JSON path comparison to match
* @param conn The connection on which the deletion should be executed
* @throws DocumentException If called on a SQLite connection
*/
def byJsonPath(tableName: String, path: String, conn: Connection): Unit =
CoreDelete.byJsonPath(tableName, path, conn)
/**
* Delete documents using a JSON Path match query (PostgreSQL only; creates connection)
*
* @param tableName The name of the table from which documents should be deleted
* @param path The JSON path comparison to match
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def byJsonPath(tableName: String, path: String): Unit =
CoreDelete.byJsonPath(tableName, path)

View File

@@ -0,0 +1,72 @@
package solutions.bitbadger.documents.scala
import solutions.bitbadger.documents.java.Document as CoreDocument
import java.sql.Connection
object Document:
/**
* Insert a new document
*
* @param tableName The table into which the document should be inserted (may include schema)
* @param document The document to be inserted
* @param conn The connection on which the query should be executed
* @throws DocumentException If IDs are misconfigured, or if the database command fails
*/
def insert[TDoc](tableName: String, document: TDoc, conn: Connection): Unit =
CoreDocument.insert(tableName, document, conn)
/**
* Insert a new document (creates connection)
*
* @param tableName The table into which the document should be inserted (may include schema)
* @param document The document to be inserted
* @throws DocumentException If IDs are misconfigured, or if the database command fails
*/
def insert[TDoc](tableName: String, document: TDoc): Unit =
CoreDocument.insert(tableName, document)
/**
* Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
*
* @param tableName The table in which the document should be saved (may include schema)
* @param document The document to be saved
* @param conn The connection on which the query should be executed
* @throws DocumentException If the database command fails
*/
def save[TDoc](tableName: String, document: TDoc, conn: Connection): Unit =
CoreDocument.save(tableName, document, conn)
/**
* Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert"; creates connection)
*
* @param tableName The table in which the document should be saved (may include schema)
* @param document The document to be saved
* @throws DocumentException If the database command fails
*/
def save[TDoc](tableName: String, document: TDoc): Unit =
CoreDocument.save(tableName, document)
/**
* Update (replace) a document by its ID
*
* @param tableName The table in which the document should be replaced (may include schema)
* @param docId The ID of the document to be replaced
* @param document The document to be replaced
* @param conn The connection on which the query should be executed
* @throws DocumentException If no dialect has been configured, or if the database command fails
*/
def update[TKey, TDoc](tableName: String, docId: TKey, document: TDoc, conn: Connection): Unit =
CoreDocument.update(tableName, docId, document, conn)
/**
* Update (replace) a document by its ID (creates connection)
*
* @param tableName The table in which the document should be replaced (may include schema)
* @param docId The ID of the document to be replaced
* @param document The document to be replaced
* @throws DocumentException If no dialect has been configured, or if the database command fails
*/
def update[TKey, TDoc](tableName: String, docId: TKey, document: TDoc): Unit =
CoreDocument.update(tableName, docId, document)

View File

@@ -0,0 +1,106 @@
package solutions.bitbadger.documents.scala
import solutions.bitbadger.documents.{Field, FieldMatch}
import solutions.bitbadger.documents.java.Exists as CoreExists
import java.sql.Connection
import _root_.scala.jdk.CollectionConverters.*
/**
* Functions to determine whether documents exist
*/
object Exists:
/**
* Determine a document's existence by its ID
*
* @param tableName The name of the table in which document existence should be checked
* @param docId The ID of the document to be checked
* @param conn The connection on which the existence check should be executed
* @return True if the document exists, false if not
* @throws DocumentException If no dialect has been configured
*/
def byId[TKey](tableName: String, docId: TKey, conn: Connection): Boolean =
CoreExists.byId(tableName, docId, conn)
/**
* Determine a document's existence by its ID (creates connection)
*
* @param tableName The name of the table in which document existence should be checked
* @param docId The ID of the document to be checked
* @return True if the document exists, false if not
* @throws DocumentException If no connection string has been set
*/
def byId[TKey](tableName: String, docId: TKey): Boolean =
CoreExists.byId(tableName, docId)
/**
* Determine document existence using a field comparison
*
* @param tableName The name of the table in which document existence should be checked
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
* @param conn The connection on which the existence check should be executed
* @return True if any matching documents exist, false if not
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def byFields(tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch], conn: Connection): Boolean =
CoreExists.byFields(tableName, fields.asJava, howMatched.orNull, conn)
/**
* Determine document existence using a field comparison (creates connection)
*
* @param tableName The name of the table in which document existence should be checked
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
* @return True if any matching documents exist, false if not
* @throws DocumentException If no connection string has been set, or if parameters are invalid
*/
def byFields(tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch] = None): Boolean =
CoreExists.byFields(tableName, fields.asJava, howMatched.orNull)
/**
* Determine document existence using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which document existence should be checked
* @param criteria The object for which JSON containment should be checked
* @param conn The connection on which the existence check should be executed
* @return True if any matching documents exist, false if not
* @throws DocumentException If called on a SQLite connection
*/
def byContains[TContains](tableName: String, criteria: TContains, conn: Connection): Boolean =
CoreExists.byContains(tableName, criteria, conn)
/**
* Determine document existence using a JSON containment query (PostgreSQL only; creates connection)
*
* @param tableName The name of the table in which document existence should be checked
* @param criteria The object for which JSON containment should be checked
* @return True if any matching documents exist, false if not
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def byContains[TContains](tableName: String, criteria: TContains): Boolean =
CoreExists.byContains(tableName, criteria)
/**
* Determine document existence using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table in which document existence should be checked
* @param path The JSON path comparison to match
* @param conn The connection on which the existence check should be executed
* @return True if any matching documents exist, false if not
* @throws DocumentException If called on a SQLite connection
*/
def byJsonPath(tableName: String, path: String, conn: Connection): Boolean =
CoreExists.byJsonPath(tableName, path, conn)
/**
* Determine document existence using a JSON Path match query (PostgreSQL only; creates connection)
*
* @param tableName The name of the table in which document existence should be checked
* @param path The JSON path comparison to match
* @return True if any matching documents exist, false if not
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def byJsonPath(tableName: String, path: String): Boolean =
CoreExists.byJsonPath(tableName, path)

View File

@@ -0,0 +1,372 @@
package solutions.bitbadger.documents.scala
import solutions.bitbadger.documents.{Configuration, Field, FieldMatch, Parameter, ParameterType}
import solutions.bitbadger.documents.query.{FindQuery, QueryUtils}
import java.sql.Connection
import scala.reflect.ClassTag
import scala.jdk.CollectionConverters.*
import scala.util.Using
/**
* Functions to find and retrieve documents
*/
object Find:
/**
* Retrieve all documents in the given table, ordering results by the optional given fields
*
* @param tableName The table from which documents should be retrieved
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @param conn The connection over which documents should be retrieved
* @return A list of documents from the given table
* @throws DocumentException If query execution fails
*/
def all[TDoc](tableName: String, orderBy: Seq[Field[?]], conn: Connection)(implicit tag: ClassTag[TDoc]): List[TDoc] =
Custom.list[TDoc](FindQuery.all(tableName) + QueryUtils.orderBy(orderBy.asJava), conn, Results.fromData)
/**
* Retrieve all documents in the given table, ordering results by the optional given fields
*
* @param tableName The table from which documents should be retrieved
* @param conn The connection over which documents should be retrieved
* @return A list of documents from the given table
* @throws DocumentException If query execution fails
*/
def all[TDoc](tableName: String, conn: Connection)(implicit tag: ClassTag[TDoc]): List[TDoc] =
all[TDoc](tableName, List(), conn)
/**
* Retrieve all documents in the given table (creates connection)
*
* @param tableName The table from which documents should be retrieved
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents from the given table
* @throws DocumentException If no connection string has been set, or if query execution fails
*/
def all[TDoc](tableName: String, orderBy: Seq[Field[?]] = List())(implicit tag: ClassTag[TDoc]): List[TDoc] =
Using(Configuration.dbConn()) { conn => all[TDoc](tableName, orderBy, conn) }.get
/**
* Retrieve a document by its ID
*
* @param tableName The table from which the document should be retrieved
* @param docId The ID of the document to retrieve
* @param conn The connection over which documents should be retrieved
* @return An `Option` with the document if it is found
* @throws DocumentException If no dialect has been configured
*/
def byId[TKey, TDoc](tableName: String, docId: TKey, conn: Connection)(implicit tag: ClassTag[TDoc]): Option[TDoc] =
Custom.single[TDoc](FindQuery.byId(tableName, docId),
Parameters.addFields(Field.equal(Configuration.idField, docId, ":id") :: Nil).toSeq, conn, Results.fromData)
/**
* Retrieve a document by its ID (creates connection
*
* @param tableName The table from which the document should be retrieved
* @param docId The ID of the document to retrieve
* @return An `Option` with the document if it is found
* @throws DocumentException If no connection string has been set
*/
def byId[TKey, TDoc](tableName: String, docId: TKey)(implicit tag: ClassTag[TDoc]): Option[TDoc] =
Using(Configuration.dbConn()) { conn => byId[TKey, TDoc](tableName, docId, conn) }.get
/**
* Retrieve documents using a field comparison, ordering results by the given fields
*
* @param tableName The table from which documents should be retrieved
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @param conn The connection over which documents should be retrieved
* @return A list of documents matching the field comparison
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def byFields[TDoc](tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch], orderBy: Seq[Field[?]],
conn: Connection)(implicit tag: ClassTag[TDoc]): List[TDoc] =
val named = Parameters.nameFields(fields)
Custom.list[TDoc](
FindQuery.byFields(tableName, named.asJava, howMatched.orNull) + QueryUtils.orderBy(orderBy.asJava),
Parameters.addFields(named).toSeq, conn, Results.fromData)
/**
* Retrieve documents using a field comparison
*
* @param tableName The table from which documents should be retrieved
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
* @param conn The connection over which documents should be retrieved
* @return A list of documents matching the field comparison
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def byFields[TDoc](tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch], conn: Connection)
(implicit tag: ClassTag[TDoc]): List[TDoc] =
byFields[TDoc](tableName, fields, howMatched, List(), conn)
/**
* Retrieve documents using a field comparison, ordering results by the given fields
*
* @param tableName The table from which documents should be retrieved
* @param fields The fields which should be compared
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @param conn The connection over which documents should be retrieved
* @return A list of documents matching the field comparison
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def byFields[TDoc](tableName: String, fields: Seq[Field[?]], orderBy: Seq[Field[?]], conn: Connection)
(implicit tag: ClassTag[TDoc]): List[TDoc] =
byFields[TDoc](tableName, fields, None, orderBy, conn)
/**
* Retrieve documents using a field comparison, ordering results by the given fields (creates connection)
*
* @param tableName The table from which documents should be retrieved
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents matching the field comparison
* @throws DocumentException If no connection string has been set, or if parameters are invalid
*/
def byFields[TDoc](tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch] = None,
orderBy: Seq[Field[?]] = List())(implicit tag: ClassTag[TDoc]): List[TDoc] =
Using(Configuration.dbConn()) { conn => byFields[TDoc](tableName, fields, howMatched, orderBy, conn) }.get
/**
* Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param criteria The object for which JSON containment should be checked
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @param conn The connection over which documents should be retrieved
* @return A list of documents matching the JSON containment query
* @throws DocumentException If called on a SQLite connection
*/
def byContains[TDoc, TContains](tableName: String, criteria: TContains, orderBy: Seq[Field[?]],
conn: Connection)(implicit tag: ClassTag[TDoc]): List[TDoc] =
Custom.list[TDoc](FindQuery.byContains(tableName) + QueryUtils.orderBy(orderBy.asJava),
Parameters.json(":criteria", criteria) :: Nil, conn, Results.fromData)
/**
* Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param criteria The object for which JSON containment should be checked
* @param conn The connection over which documents should be retrieved
* @return A list of documents matching the JSON containment query
* @throws DocumentException If called on a SQLite connection
*/
def byContains[TDoc, TContains](tableName: String, criteria: TContains, conn: Connection)
(implicit tag: ClassTag[TDoc]): List[TDoc] =
byContains[TDoc, TContains](tableName, criteria, List(), conn)
/**
* Retrieve documents using a JSON containment query, ordering results by the given fields (PostgreSQL only; creates
* connection)
*
* @param tableName The table from which documents should be retrieved
* @param criteria The object for which JSON containment should be checked
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents matching the JSON containment query
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def byContains[TDoc, TContains](tableName: String, criteria: TContains, orderBy: Seq[Field[?]] = List())
(implicit tag: ClassTag[TDoc]): List[TDoc] =
Using(Configuration.dbConn()) { conn => byContains[TDoc, TContains](tableName, criteria, orderBy, conn) }.get
/**
* Retrieve documents using a JSON Path match query, ordering results by the given fields (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param path The JSON path comparison to match
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @param conn The connection over which documents should be retrieved
* @return A list of documents matching the JSON Path match query
* @throws DocumentException If called on a SQLite connection
*/
def byJsonPath[TDoc](tableName: String, path: String, orderBy: Seq[Field[?]], conn: Connection)
(implicit tag: ClassTag[TDoc]): List[TDoc] =
Custom.list[TDoc](FindQuery.byJsonPath(tableName) + QueryUtils.orderBy(orderBy.asJava),
Parameter(":path", ParameterType.STRING, path) :: Nil, conn, Results.fromData)
/**
* Retrieve documents using a JSON Path match query (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param path The JSON path comparison to match
* @param conn The connection over which documents should be retrieved
* @return A list of documents matching the JSON Path match query
* @throws DocumentException If called on a SQLite connection
*/
def byJsonPath[TDoc](tableName: String, path: String, conn: Connection)(implicit tag: ClassTag[TDoc]): List[TDoc] =
byJsonPath[TDoc](tableName, path, List(), conn)
/**
* Retrieve documents using a JSON Path match query, ordering results by the given fields (PostgreSQL only; creates
* connection)
*
* @param tableName The table from which documents should be retrieved
* @param path The JSON path comparison to match
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents matching the JSON Path match query
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def byJsonPath[TDoc](tableName: String, path: String, orderBy: Seq[Field[?]] = List())
(implicit tag: ClassTag[TDoc]): List[TDoc] =
Using(Configuration.dbConn()) { conn => byJsonPath[TDoc](tableName, path, orderBy, conn) }.get
/**
* Retrieve the first document using a field comparison and ordering fields
*
* @param tableName The table from which documents should be retrieved
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @param conn The connection over which documents should be retrieved
* @return An `Option` with the first document matching the field comparison if found
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def firstByFields[TDoc](tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch],
orderBy: Seq[Field[?]], conn: Connection)(implicit tag: ClassTag[TDoc]): Option[TDoc] =
val named = Parameters.nameFields(fields)
Custom.single[TDoc](
FindQuery.byFields(tableName, named.asJava, howMatched.orNull) + QueryUtils.orderBy(orderBy.asJava),
Parameters.addFields(named).toSeq, conn, Results.fromData)
/**
* Retrieve the first document using a field comparison
*
* @param tableName The table from which documents should be retrieved
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
* @param conn The connection over which documents should be retrieved
* @return An `Option` with the first document matching the field comparison if found
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def firstByFields[TDoc](tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch], conn: Connection)
(implicit tag: ClassTag[TDoc]): Option[TDoc] =
firstByFields[TDoc](tableName, fields, howMatched, List(), conn)
/**
* Retrieve the first document using a field comparison and ordering fields
*
* @param tableName The table from which documents should be retrieved
* @param fields The fields which should be compared
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @param conn The connection over which documents should be retrieved
* @return An `Option` with the first document matching the field comparison if found
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def firstByFields[TDoc](tableName: String, fields: Seq[Field[?]], orderBy: Seq[Field[?]], conn: Connection)
(implicit tag: ClassTag[TDoc]): Option[TDoc] =
firstByFields[TDoc](tableName, fields, None, orderBy, conn)
/**
* Retrieve the first document using a field comparison
*
* @param tableName The table from which documents should be retrieved
* @param fields The fields which should be compared
* @param conn The connection over which documents should be retrieved
* @return An `Option` with the first document matching the field comparison if found
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def firstByFields[TDoc](tableName: String, fields: Seq[Field[?]], conn: Connection)
(implicit tag: ClassTag[TDoc]): Option[TDoc] =
firstByFields[TDoc](tableName, fields, None, List(), conn)
/**
* Retrieve the first document using a field comparison and optional ordering fields (creates connection)
*
* @param tableName The table from which documents should be retrieved
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return An `Option` with the first document matching the field comparison if found
* @throws DocumentException If no connection string has been set, or if parameters are invalid
*/
def firstByFields[TDoc](tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch] = None,
orderBy: Seq[Field[?]] = List())(implicit tag: ClassTag[TDoc]): Option[TDoc] =
Using(Configuration.dbConn()) { conn => firstByFields[TDoc](tableName, fields, howMatched, orderBy, conn) }.get
/**
* Retrieve the first document using a JSON containment query and ordering fields (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param criteria The object for which JSON containment should be checked
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @param conn The connection over which documents should be retrieved
* @return An `Option` with the first document matching the JSON containment query if found
* @throws DocumentException If called on a SQLite connection
*/
def firstByContains[TDoc, TContains](tableName: String, criteria: TContains, orderBy: Seq[Field[?]],
conn: Connection)(implicit tag: ClassTag[TDoc]): Option[TDoc] =
Custom.single[TDoc](FindQuery.byContains(tableName) + QueryUtils.orderBy(orderBy.asJava),
Parameters.json(":criteria", criteria) :: Nil, conn, Results.fromData)
/**
* Retrieve the first document using a JSON containment query (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param criteria The object for which JSON containment should be checked
* @param conn The connection over which documents should be retrieved
* @return An `Option` with the first document matching the JSON containment query if found
* @throws DocumentException If called on a SQLite connection
*/
def firstByContains[TDoc, TContains](tableName: String, criteria: TContains, conn: Connection)
(implicit tag: ClassTag[TDoc]): Option[TDoc] =
firstByContains[TDoc, TContains](tableName, criteria, List(), conn)
/**
* Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only; creates
* connection)
*
* @param tableName The table from which documents should be retrieved
* @param criteria The object for which JSON containment should be checked
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return An `Option` with the first document matching the JSON containment query if found
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def firstByContains[TDoc, TContains](tableName: String, criteria: TContains, orderBy: Seq[Field[?]] = List())
(implicit tag: ClassTag[TDoc]): Option[TDoc] =
Using(Configuration.dbConn()) { conn => firstByContains[TDoc, TContains](tableName, criteria, orderBy, conn) }.get
/**
* Retrieve the first document using a JSON Path match query and ordering fields (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param path The JSON path comparison to match
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @param conn The connection over which documents should be retrieved
* @return An `Optional` item, with the first document matching the JSON Path match query if found
* @throws DocumentException If called on a SQLite connection
*/
def firstByJsonPath[TDoc](tableName: String, path: String, orderBy: Seq[Field[?]], conn: Connection)
(implicit tag: ClassTag[TDoc]): Option[TDoc] =
Custom.single[TDoc](FindQuery.byJsonPath(tableName) + QueryUtils.orderBy(orderBy.asJava),
Parameter(":path", ParameterType.STRING, path) :: Nil, conn, Results.fromData)
/**
* Retrieve the first document using a JSON Path match query (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param path The JSON path comparison to match
* @param conn The connection over which documents should be retrieved
* @return An `Option` with the first document matching the JSON Path match query if found
* @throws DocumentException If called on a SQLite connection
*/
def firstByJsonPath[TDoc](tableName: String, path: String, conn: Connection)
(implicit tag: ClassTag[TDoc]): Option[TDoc] =
firstByJsonPath[TDoc](tableName, path, List(), conn)
/**
* Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only; creates
* connection)
*
* @param tableName The table from which documents should be retrieved
* @param path The JSON path comparison to match
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return An `Optional` item, with the first document matching the JSON Path match query if found
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def firstByJsonPath[TDoc](tableName: String, path: String, orderBy: Seq[Field[?]] = List())
(implicit tag: ClassTag[TDoc]): Option[TDoc] =
Using(Configuration.dbConn()) { conn => firstByJsonPath[TDoc](tableName, path, orderBy, conn) }.get

View File

@@ -0,0 +1,87 @@
package solutions.bitbadger.documents.scala
import solutions.bitbadger.documents.{Field, Op, Parameter, ParameterName}
import solutions.bitbadger.documents.java.Parameters as CoreParameters
import java.sql.{Connection, PreparedStatement}
import java.util
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
import _root_.scala.jdk.CollectionConverters.*
/**
* Functions to assist with the creation and implementation of parameters for SQL queries
*/
object Parameters:
/**
* Assign parameter names to any fields that do not have them assigned
*
* @param fields The collection of fields to be named
* @return The collection of fields with parameter names assigned
*/
def nameFields(fields: Seq[Field[?]]): Seq[Field[?]] =
val name = ParameterName()
fields.map { it =>
if ((it.getParameterName == null || it.getParameterName.isEmpty)
&& !(Op.EXISTS :: Op.NOT_EXISTS :: Nil).contains(it.getComparison.getOp)) {
it.withParameterName(name.derive(null))
} else {
it
}
}
/**
* Create a parameter by encoding a JSON object
*
* @param name The parameter name
* @param value The object to be encoded as JSON
* @return A parameter with the value encoded
*/
def json[T](name: String, value: T): Parameter[String] =
CoreParameters.json(name, value)
/**
* Add field parameters to the given set of parameters
*
* @param fields The fields being compared in the query
* @param existing Any existing parameters for the query (optional, defaults to empty collection)
* @return A collection of parameters for the query
*/
def addFields(fields: Seq[Field[?]],
existing: mutable.Buffer[Parameter[?]] = ListBuffer()): mutable.Buffer[Parameter[?]] =
fields.foreach { it => it.appendParameter(new util.ArrayList[Parameter[?]]()).forEach(existing.append) }
existing
/**
* Replace the parameter names in the query with question marks
*
* @param query The query with named placeholders
* @param parameters The parameters for the query
* @return The query, with name parameters changed to `?`s
*/
def replaceNamesInQuery(query: String, parameters: Seq[Parameter[?]]): String =
CoreParameters.replaceNamesInQuery(query, parameters.asJava)
/**
* Apply the given parameters to the given query, returning a prepared statement
*
* @param conn The active JDBC connection
* @param query The query
* @param parameters The parameters for the query
* @return A `PreparedStatement` with the parameter names replaced with `?` and parameter values bound
* @throws DocumentException If parameter names are invalid or number value types are invalid
*/
def apply(conn: Connection, query: String, parameters: Seq[Parameter[?]]): PreparedStatement =
CoreParameters.apply(conn, query, parameters.asJava)
/**
* Create parameters for field names to be removed from a document
*
* @param names The names of the fields to be removed
* @param parameterName The parameter name to use for the query
* @return A list of parameters to use for building the query
* @throws DocumentException If the dialect has not been set
*/
def fieldNames(names: Seq[String], parameterName: String = ":name"): mutable.Buffer[Parameter[?]] =
CoreParameters.fieldNames(names.asJava, parameterName).asScala.toBuffer

View File

@@ -0,0 +1,120 @@
package solutions.bitbadger.documents.scala
import solutions.bitbadger.documents.{Field, FieldMatch}
import solutions.bitbadger.documents.java.Patch as CorePatch
import java.sql.Connection
import _root_.scala.jdk.CollectionConverters.*
/**
* Functions to patch (partially update) documents
*/
object Patch:
/**
* Patch a document by its ID
*
* @param tableName The name of the table in which a document should be patched
* @param docId The ID of the document to be patched
* @param patch The object whose properties should be replaced in the document
* @param conn The connection on which the update should be executed
* @throws DocumentException If no dialect has been configured
*/
def byId[TKey, TPatch](tableName: String, docId: TKey, patch: TPatch, conn: Connection): Unit =
CorePatch.byId(tableName, docId, patch, conn)
/**
* Patch a document by its ID (creates connection)
*
* @param tableName The name of the table in which a document should be patched
* @param docId The ID of the document to be patched
* @param patch The object whose properties should be replaced in the document
* @throws DocumentException If no connection string has been set
*/
def byId[TKey, TPatch](tableName: String, docId: TKey, patch: TPatch) =
CorePatch.byId(tableName, docId, patch)
/**
* Patch documents using a field comparison
*
* @param tableName The name of the table in which documents should be patched
* @param fields The fields which should be compared
* @param patch The object whose properties should be replaced in the document
* @param howMatched How the fields should be matched
* @param conn The connection on which the update should be executed
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def byFields[TPatch](tableName: String, fields: Seq[Field[?]], patch: TPatch, howMatched: Option[FieldMatch],
conn: Connection): Unit =
CorePatch.byFields(tableName, fields.asJava, patch, howMatched.orNull, conn)
/**
* Patch documents using a field comparison
*
* @param tableName The name of the table in which documents should be patched
* @param fields The fields which should be compared
* @param patch The object whose properties should be replaced in the document
* @param conn The connection on which the update should be executed
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def byFields[TPatch](tableName: String, fields: Seq[Field[?]], patch: TPatch, conn: Connection): Unit =
byFields(tableName, fields, patch, None, conn)
/**
* Patch documents using a field comparison (creates connection)
*
* @param tableName The name of the table in which documents should be patched
* @param fields The fields which should be compared
* @param patch The object whose properties should be replaced in the document
* @param howMatched How the fields should be matched
* @throws DocumentException If no connection string has been set, or if parameters are invalid
*/
def byFields[TPatch](tableName: String, fields: Seq[Field[?]], patch: TPatch,
howMatched: Option[FieldMatch] = None): Unit =
CorePatch.byFields(tableName, fields.asJava, patch, howMatched.orNull)
/**
* Patch documents using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which documents should be patched
* @param criteria The object against which JSON containment should be checked
* @param patch The object whose properties should be replaced in the document
* @param conn The connection on which the update should be executed
* @throws DocumentException If called on a SQLite connection
*/
def byContains[TContains, TPatch](tableName: String, criteria: TContains, patch: TPatch, conn: Connection): Unit =
CorePatch.byContains(tableName, criteria, patch, conn)
/**
* Patch documents using a JSON containment query (PostgreSQL only; creates connection)
*
* @param tableName The name of the table in which documents should be patched
* @param criteria The object against which JSON containment should be checked
* @param patch The object whose properties should be replaced in the document
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def byContains[TContains, TPatch](tableName: String, criteria: TContains, patch: TPatch): Unit =
CorePatch.byContains(tableName, criteria, patch)
/**
* Patch documents using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table in which documents should be patched
* @param path The JSON path comparison to match
* @param patch The object whose properties should be replaced in the document
* @param conn The connection on which the update should be executed
* @throws DocumentException If called on a SQLite connection
*/
def byJsonPath[TPatch](tableName: String, path: String, patch: TPatch, conn: Connection): Unit =
CorePatch.byJsonPath(tableName, path, patch, conn)
/**
* Patch documents using a JSON Path match query (PostgreSQL only; creates connection)
*
* @param tableName The name of the table in which documents should be patched
* @param path The JSON path comparison to match
* @param patch The object whose properties should be replaced in the document
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def byJsonPath[TPatch](tableName: String, path: String, patch: TPatch): Unit =
CorePatch.byJsonPath(tableName, path, patch)

View File

@@ -0,0 +1,120 @@
package solutions.bitbadger.documents.scala
import solutions.bitbadger.documents.{Field, FieldMatch}
import solutions.bitbadger.documents.java.RemoveFields as CoreRemoveFields
import java.sql.Connection
import scala.jdk.CollectionConverters.*
/**
* Functions to remove fields from documents
*/
object RemoveFields:
/**
* Remove fields from a document by its ID
*
* @param tableName The name of the table in which the document's fields should be removed
* @param docId The ID of the document to have fields removed
* @param toRemove The names of the fields to be removed
* @param conn The connection on which the update should be executed
* @throws DocumentException If no dialect has been configured
*/
def byId[TKey](tableName: String, docId: TKey, toRemove: Seq[String], conn: Connection): Unit =
CoreRemoveFields.byId(tableName, docId, toRemove.asJava, conn)
/**
* Remove fields from a document by its ID (creates connection)
*
* @param tableName The name of the table in which the document's fields should be removed
* @param docId The ID of the document to have fields removed
* @param toRemove The names of the fields to be removed
* @throws DocumentException If no connection string has been set
*/
def byId[TKey](tableName: String, docId: TKey, toRemove: Seq[String]): Unit =
CoreRemoveFields.byId(tableName, docId, toRemove.asJava)
/**
* Remove fields from documents using a field comparison
*
* @param tableName The name of the table in which document fields should be removed
* @param fields The fields which should be compared
* @param toRemove The names of the fields to be removed
* @param howMatched How the fields should be matched
* @param conn The connection on which the update should be executed
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def byFields(tableName: String, fields: Seq[Field[?]], toRemove: Seq[String], howMatched: Option[FieldMatch],
conn: Connection): Unit =
CoreRemoveFields.byFields(tableName, fields.asJava, toRemove.asJava, howMatched.orNull, conn)
/**
* Remove fields from documents using a field comparison
*
* @param tableName The name of the table in which document fields should be removed
* @param fields The fields which should be compared
* @param toRemove The names of the fields to be removed
* @param conn The connection on which the update should be executed
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def byFields(tableName: String, fields: Seq[Field[?]], toRemove: Seq[String], conn: Connection): Unit =
byFields(tableName, fields, toRemove, None, conn)
/**
* Remove fields from documents using a field comparison (creates connection)
*
* @param tableName The name of the table in which document fields should be removed
* @param fields The fields which should be compared
* @param toRemove The names of the fields to be removed
* @param howMatched How the fields should be matched
* @throws DocumentException If no connection string has been set, or if parameters are invalid
*/
def byFields(tableName: String, fields: Seq[Field[?]], toRemove: Seq[String],
howMatched: Option[FieldMatch] = None): Unit =
CoreRemoveFields.byFields(tableName, fields.asJava, toRemove.asJava, howMatched.orNull)
/**
* Remove fields from documents using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which document fields should be removed
* @param criteria The object against which JSON containment should be checked
* @param toRemove The names of the fields to be removed
* @param conn The connection on which the update should be executed
* @throws DocumentException If called on a SQLite connection
*/
def byContains[TContains](tableName: String, criteria: TContains, toRemove: Seq[String], conn: Connection): Unit =
CoreRemoveFields.byContains(tableName, criteria, toRemove.asJava, conn)
/**
* Remove fields from documents using a JSON containment query (PostgreSQL only; creates connection)
*
* @param tableName The name of the table in which document fields should be removed
* @param criteria The object against which JSON containment should be checked
* @param toRemove The names of the fields to be removed
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def byContains[TContains](tableName: String, criteria: TContains, toRemove: Seq[String]): Unit =
CoreRemoveFields.byContains(tableName, criteria, toRemove.asJava)
/**
* Remove fields from documents using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table in which document fields should be removed
* @param path The JSON path comparison to match
* @param toRemove The names of the fields to be removed
* @param conn The connection on which the update should be executed
* @throws DocumentException If called on a SQLite connection
*/
def byJsonPath(tableName: String, path: String, toRemove: Seq[String], conn: Connection): Unit =
CoreRemoveFields.byJsonPath(tableName, path, toRemove.asJava, conn)
/**
* Remove fields from documents using a JSON Path match query (PostgreSQL only; creates connection)
*
* @param tableName The name of the table in which document fields should be removed
* @param path The JSON path comparison to match
* @param toRemove The names of the fields to be removed
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def byJsonPath(tableName: String, path: String, toRemove: Seq[String]): Unit =
CoreRemoveFields.byJsonPath(tableName, path, toRemove.asJava)

View File

@@ -0,0 +1,77 @@
package solutions.bitbadger.documents.scala
import solutions.bitbadger.documents.DocumentException
import solutions.bitbadger.documents.java.Results as CoreResults
import java.sql.{PreparedStatement, ResultSet, SQLException}
import scala.collection.mutable.ListBuffer
import scala.reflect.ClassTag
import scala.util.Using
/**
* Functions to manipulate results
*/
object Results:
/**
* Create a domain item from a document, specifying the field in which the document is found
*
* @param field The field name containing the JSON document
* @param rs A `ResultSet` set to the row with the document to be constructed
* @param ignored The class tag (placeholder used for signature; implicit tag used for serialization)
* @return The constructed domain item
*/
def fromDocument[TDoc](field: String, rs: ResultSet, ignored: ClassTag[TDoc])(implicit tag: ClassTag[TDoc]): TDoc =
CoreResults.fromDocument(field, rs, tag.runtimeClass.asInstanceOf[Class[TDoc]])
/**
* Create a domain item from a document
*
* @param rs A `ResultSet` set to the row with the document to be constructed
* @param ignored The class tag (placeholder used for signature; implicit tag used for serialization)
* @return The constructed domain item
*/
def fromData[TDoc](rs: ResultSet, ignored: ClassTag[TDoc])(implicit tag: ClassTag[TDoc]): TDoc =
fromDocument[TDoc]("data", rs, tag)
/**
* Create a list of items for the results of the given command, using the specified mapping function
*
* @param stmt The prepared statement to execute
* @param mapFunc The mapping function from data reader to domain class instance
* @return A list of items from the query's result
* @throws DocumentException If there is a problem executing the query (unchecked)
*/
def toCustomList[TDoc](stmt: PreparedStatement,
mapFunc: (ResultSet, ClassTag[TDoc]) => TDoc)(implicit tag: ClassTag[TDoc]): List[TDoc] =
try
val buffer = ListBuffer[TDoc]()
Using(stmt.executeQuery()) { rs =>
while (rs.next()) {
buffer.append(mapFunc(rs, tag))
}
}
buffer.toList
catch
case ex: SQLException =>
throw DocumentException("Error retrieving documents from query: ${ex.message}", ex)
/**
* Extract a count from the first column
*
* @param rs A `ResultSet` set to the row with the count to retrieve
* @return The count from the row
* @throws DocumentException If the dialect has not been set (unchecked)
*/
def toCount(rs: ResultSet, tag: ClassTag[Long] = ClassTag.Long): Long =
CoreResults.toCount(rs, Long.getClass)
/**
* Extract a true/false value from the first column
*
* @param rs A `ResultSet` set to the row with the true/false value to retrieve
* @return The true/false value from the row
* @throws DocumentException If the dialect has not been set (unchecked)
*/
def toExists(rs: ResultSet, tag: ClassTag[Boolean] = ClassTag.Boolean): Boolean =
CoreResults.toExists(rs, Boolean.getClass)

View File

@@ -0,0 +1,499 @@
package solutions.bitbadger.documents.scala
import solutions.bitbadger.documents.{DocumentIndex, Field, FieldMatch, Parameter}
import java.sql.{Connection, ResultSet}
import scala.reflect.ClassTag
package object extensions:
extension (conn: Connection)
// ~~~ CUSTOM QUERIES ~~~
/**
* Execute a query that returns a list of results
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
* @param mapFunc The mapping function between the document and the domain item
* @return A list of results for the given query
* @throws DocumentException If parameters are invalid
*/
def customList[TDoc](query: String, parameters: Seq[Parameter[?]],
mapFunc: (ResultSet, ClassTag[TDoc]) => TDoc)(implicit tag: ClassTag[TDoc]): List[TDoc] =
Custom.list[TDoc](query, parameters, conn, mapFunc)
/**
* Execute a query that returns a list of results
*
* @param query The query to retrieve the results
* @param mapFunc The mapping function between the document and the domain item
* @return A list of results for the given query
* @throws DocumentException If parameters are invalid
*/
def customList[TDoc](query: String,
mapFunc: (ResultSet, ClassTag[TDoc]) => TDoc)(implicit tag: ClassTag[TDoc]): List[TDoc] =
Custom.list[TDoc](query, conn, mapFunc)
/**
* Execute a query that returns one or no results
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
* @param mapFunc The mapping function between the document and the domain item
* @return An optional document, filled if one matches the query
* @throws DocumentException If parameters are invalid
*/
def customSingle[TDoc](query: String, parameters: Seq[Parameter[?]],
mapFunc: (ResultSet, ClassTag[TDoc]) => TDoc)(implicit tag: ClassTag[TDoc]): Option[TDoc] =
Custom.single[TDoc](query, parameters, conn, mapFunc)
/**
* Execute a query that returns one or no results
*
* @param query The query to retrieve the results
* @param mapFunc The mapping function between the document and the domain item
* @return An optional document, filled if one matches the query
* @throws DocumentException If parameters are invalid
*/
def customSingle[TDoc](query: String,
mapFunc: (ResultSet, ClassTag[TDoc]) => TDoc)(implicit tag: ClassTag[TDoc]): Option[TDoc] =
Custom.single[TDoc](query, conn, mapFunc)
/**
* Execute a query that returns no results
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
* @throws DocumentException If parameters are invalid
*/
def customNonQuery(query: String, parameters: Seq[Parameter[?]] = List()): Unit =
Custom.nonQuery(query, parameters, conn)
/**
* Execute a query that returns a scalar result
*
* @param query The query to retrieve the result
* @param parameters Parameters to use for the query
* @param mapFunc The mapping function between the document and the domain item
* @return The scalar value from the query
* @throws DocumentException If parameters are invalid
*/
def customScalar[T](query: String, parameters: Seq[Parameter[?]],
mapFunc: (ResultSet, ClassTag[T]) => T)(implicit tag: ClassTag[T]): T =
Custom.scalar[T](query, parameters, conn, mapFunc)
/**
* Execute a query that returns a scalar result
*
* @param query The query to retrieve the result
* @param mapFunc The mapping function between the document and the domain item
* @return The scalar value from the query
* @throws DocumentException If parameters are invalid
*/
def customScalar[T](query: String,
mapFunc: (ResultSet, ClassTag[T]) => T)(implicit tag: ClassTag[T]): T =
Custom.scalar[T](query, conn, mapFunc)
// ~~~ DEFINITION QUERIES ~~~
/**
* Create a document table if necessary
*
* @param tableName The table whose existence should be ensured (may include schema)
* @throws DocumentException If the dialect is not configured
*/
def ensureTable(tableName: String): Unit =
Definition.ensureTable(tableName, conn)
/**
* Create an index on field(s) within documents in the specified table if necessary
*
* @param tableName The table to be indexed (may include schema)
* @param indexName The name of the index to create
* @param fields One or more fields to be indexed<
* @throws DocumentException If any dependent process does
*/
def ensureFieldIndex(tableName: String, indexName: String, fields: Seq[String]): Unit =
Definition.ensureFieldIndex(tableName, indexName, fields, conn)
/**
* Create a document index on a table (PostgreSQL only)
*
* @param tableName The table to be indexed (may include schema)
* @param indexType The type of index to ensure
* @throws DocumentException If called on a SQLite connection
*/
def ensureDocumentIndex(tableName: String, indexType: DocumentIndex): Unit =
Definition.ensureDocumentIndex (tableName, indexType, conn)
// ~~~ DOCUMENT MANIPULATION QUERIES ~~~
/**
* Insert a new document
*
* @param tableName The table into which the document should be inserted (may include schema)
* @param document The document to be inserted
* @throws DocumentException If IDs are misconfigured, or if the database command fails
*/
def insert[TDoc](tableName: String, document: TDoc): Unit =
Document.insert(tableName, document, conn)
/**
* Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
*
* @param tableName The table in which the document should be saved (may include schema)
* @param document The document to be saved
* @throws DocumentException If the database command fails
*/
def save[TDoc](tableName: String, document: TDoc): Unit =
Document.save(tableName, document, conn)
/**
* Update (replace) a document by its ID
*
* @param tableName The table in which the document should be replaced (may include schema)
* @param docId The ID of the document to be replaced
* @param document The document to be replaced
* @throws DocumentException If no dialect has been configured, or if the database command fails
*/
def update[TKey, TDoc](tableName: String, docId: TKey, document: TDoc): Unit =
Document.update(tableName, docId, document, conn)
// ~~~ DOCUMENT COUNT QUERIES ~~~
/**
* Count all documents in the table
*
* @param tableName The name of the table in which documents should be counted
* @return A count of the documents in the table
* @throws DocumentException If any dependent process does
*/
def countAll(tableName: String): Long =
Count.all(tableName, conn)
/**
* Count documents using a field comparison
*
* @param tableName The name of the table in which documents should be counted
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched (optional, default `ALL`)
* @return A count of the matching documents in the table
* @throws DocumentException If the dialect has not been configured
*/
def countByFields(tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch] = None): Long =
Count.byFields(tableName, fields, howMatched, conn)
/**
* Count documents using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which documents should be counted
* @param criteria The object for which JSON containment should be checked
* @return A count of the matching documents in the table
* @throws DocumentException If called on a SQLite connection
*/
def countByContains[TContains](tableName: String, criteria: TContains): Long =
Count.byContains(tableName, criteria, conn)
/**
* Count documents using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table in which documents should be counted
* @param path The JSON path comparison to match
* @return A count of the matching documents in the table
* @throws DocumentException If called on a SQLite connection
*/
def countByJsonPath(tableName: String, path: String): Long =
Count.byJsonPath(tableName, path, conn)
// ~~~ DOCUMENT EXISTENCE QUERIES ~~~
/**
* Determine a document's existence by its ID
*
* @param tableName The name of the table in which document existence should be checked
* @param docId The ID of the document to be checked
* @return True if the document exists, false if not
* @throws DocumentException If no dialect has been configured
*/
def existsById[TKey](tableName: String, docId: TKey): Boolean =
Exists.byId(tableName, docId, conn)
/**
* Determine document existence using a field comparison
*
* @param tableName The name of the table in which document existence should be checked
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
* @return True if any matching documents exist, false if not
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def existsByFields(tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch] = None): Boolean =
Exists.byFields(tableName, fields, howMatched, conn)
/**
* Determine document existence using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which document existence should be checked
* @param criteria The object for which JSON containment should be checked
* @return True if any matching documents exist, false if not
* @throws DocumentException If called on a SQLite connection
*/
def existsByContains[TContains](tableName: String, criteria: TContains): Boolean =
Exists.byContains(tableName, criteria, conn)
/**
* Determine document existence using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table in which document existence should be checked
* @param path The JSON path comparison to match
* @return True if any matching documents exist, false if not
* @throws DocumentException If called on a SQLite connection
*/
def existsByJsonPath(tableName: String, path: String): Boolean =
Exists.byJsonPath(tableName, path, conn)
// ~~~ DOCUMENT RETRIEVAL QUERIES ~~~
/**
* Retrieve all documents in the given table, ordering results by the optional given fields
*
* @param tableName The table from which documents should be retrieved
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents from the given table
* @throws DocumentException If query execution fails
*/
def findAll[TDoc](tableName: String, orderBy: Seq[Field[?]] = List())(implicit tag: ClassTag[TDoc]): List[TDoc] =
Find.all[TDoc](tableName, orderBy, conn)
/**
* Retrieve a document by its ID
*
* @param tableName The table from which the document should be retrieved
* @param docId The ID of the document to retrieve
* @return The document if it is found, `None` otherwise
* @throws DocumentException If no dialect has been configured
*/
def findById[TKey, TDoc](tableName: String, docId: TKey)(implicit tag: ClassTag[TDoc]): Option[TDoc] =
Find.byId[TKey, TDoc](tableName, docId, conn)
/**
* Retrieve documents using a field comparison, ordering results by the optional given fields
*
* @param tableName The table from which the document should be retrieved
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents matching the field comparison
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def findByFields[TDoc](tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch] = None,
orderBy: Seq[Field[?]] = List())(implicit tag: ClassTag[TDoc]): List[TDoc] =
Find.byFields[TDoc](tableName, fields, howMatched, orderBy, conn)
/**
* Retrieve documents using a JSON containment query, ordering results by the optional given fields (PostgreSQL
* only)
*
* @param tableName The name of the table in which document existence should be checked
* @param criteria The object for which JSON containment should be checked
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents matching the JSON containment query
* @throws DocumentException If called on a SQLite connection
*/
def findByContains[TDoc, TContains](tableName: String, criteria: TContains, orderBy: Seq[Field[?]] = List())
(implicit tag: ClassTag[TDoc]): List[TDoc] =
Find.byContains[TDoc, TContains](tableName, criteria, orderBy, conn)
/**
* Retrieve documents using a JSON Path match query, ordering results by the optional given fields (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param path The JSON path comparison to match
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A list of documents matching the JSON Path match query
* @throws DocumentException If called on a SQLite connection
*/
def findByJsonPath[TDoc](tableName: String, path: String, orderBy: Seq[Field[?]] = List())
(implicit tag: ClassTag[TDoc]): List[TDoc] =
Find.byJsonPath[TDoc](tableName, path, orderBy, conn)
/**
* Retrieve the first document using a field comparison and optional ordering fields
*
* @param tableName The table from which documents should be retrieved
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return The first document matching the field comparison, or `None` if no matches are found
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def findFirstByFields[TDoc](tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch] = None,
orderBy: Seq[Field[?]] = List())(implicit tag: ClassTag[TDoc]): Option[TDoc] =
Find.firstByFields[TDoc](tableName, fields, howMatched, orderBy, conn)
/**
* Retrieve the first document using a JSON containment query and optional ordering fields (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param criteria The object for which JSON containment should be checked
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return The first document matching the JSON containment query, or `None` if no matches are found
* @throws DocumentException If called on a SQLite connection
*/
def findFirstByContains[TDoc, TContains](tableName: String, criteria: TContains, orderBy: Seq[Field[?]] = List())
(implicit tag: ClassTag[TDoc]): Option[TDoc] =
Find.firstByContains[TDoc, TContains](tableName, criteria, orderBy, conn)
/**
* Retrieve the first document using a JSON Path match query and optional ordering fields (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param path The JSON path comparison to match
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return The first document matching the JSON Path match query, or `None` if no matches are found
* @throws DocumentException If called on a SQLite connection
*/
def findFirstByJsonPath[TDoc](tableName: String, path: String, orderBy: Seq[Field[?]] = List())
(implicit tag: ClassTag[TDoc]): Option[TDoc] =
Find.firstByJsonPath[TDoc](tableName, path, orderBy, conn)
// ~~~ DOCUMENT PATCH (PARTIAL UPDATE) QUERIES ~~~
/**
* Patch a document by its ID
*
* @param tableName The name of the table in which a document should be patched
* @param docId The ID of the document to be patched
* @param patch The object whose properties should be replaced in the document
* @throws DocumentException If no dialect has been configured
*/
def patchById[TKey, TPatch](tableName: String, docId: TKey, patch: TPatch): Unit =
Patch.byId(tableName, docId, patch, conn)
/**
* Patch documents using a field comparison
*
* @param tableName The name of the table in which documents should be patched
* @param fields The fields which should be compared
* @param patch The object whose properties should be replaced in the document
* @param howMatched How the fields should be matched
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def patchByFields[TPatch](tableName: String, fields: Seq[Field[?]], patch: TPatch,
howMatched: Option[FieldMatch] = None): Unit =
Patch.byFields(tableName, fields, patch, howMatched, conn)
/**
* Patch documents using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which documents should be patched
* @param criteria The object against which JSON containment should be checked
* @param patch The object whose properties should be replaced in the document
* @throws DocumentException If called on a SQLite connection
*/
def patchByContains[TContains, TPatch](tableName: String, criteria: TContains, patch: TPatch): Unit =
Patch.byContains(tableName, criteria, patch, conn)
/**
* Patch documents using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table in which documents should be patched
* @param path The JSON path comparison to match
* @param patch The object whose properties should be replaced in the document
* @throws DocumentException If called on a SQLite connection
*/
def patchByJsonPath[TPatch](tableName: String, path: String, patch: TPatch): Unit =
Patch.byJsonPath(tableName, path, patch, conn)
// ~~~ DOCUMENT FIELD REMOVAL QUERIES ~~~
/**
* Remove fields from a document by its ID
*
* @param tableName The name of the table in which the document's fields should be removed
* @param docId The ID of the document to have fields removed
* @param toRemove The names of the fields to be removed
* @throws DocumentException If no dialect has been configured
*/
def removeFieldsById[TKey](tableName: String, docId: TKey, toRemove: Seq[String]): Unit =
RemoveFields.byId(tableName, docId, toRemove, conn)
/**
* Remove fields from documents using a field comparison
*
* @param tableName The name of the table in which document fields should be removed
* @param fields The fields which should be compared
* @param toRemove The names of the fields to be removed
* @param howMatched How the fields should be matched
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def removeFieldsByFields(tableName: String, fields: Seq[Field[?]], toRemove: Seq[String],
howMatched: Option[FieldMatch] = None): Unit =
RemoveFields.byFields(tableName, fields, toRemove, howMatched, conn)
/**
* Remove fields from documents using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table in which document fields should be removed
* @param criteria The object against which JSON containment should be checked
* @param toRemove The names of the fields to be removed
* @throws DocumentException If called on a SQLite connection
*/
def removeFieldsByContains[TContains](tableName: String, criteria: TContains, toRemove: Seq[String]): Unit =
RemoveFields.byContains(tableName, criteria, toRemove, conn)
/**
* Remove fields from documents using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table in which document fields should be removed
* @param path The JSON path comparison to match
* @param toRemove The names of the fields to be removed
* @throws DocumentException If called on a SQLite connection
*/
def removeFieldsByJsonPath(tableName: String, path: String, toRemove: Seq[String]): Unit =
RemoveFields.byJsonPath(tableName, path, toRemove, conn)
// ~~~ DOCUMENT DELETION QUERIES ~~~
/**
* Delete a document by its ID
*
* @param tableName The name of the table from which documents should be deleted
* @param docId The ID of the document to be deleted
* @throws DocumentException If no dialect has been configured
*/
def deleteById[TKey](tableName: String, docId: TKey): Unit =
Delete.byId(tableName, docId, conn)
/**
* Delete documents using a field comparison
*
* @param tableName The name of the table from which documents should be deleted
* @param fields The fields which should be compared
* @param howMatched How the fields should be matched
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def deleteByFields(tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch] = None): Unit =
Delete.byFields(tableName, fields, howMatched, conn)
/**
* Delete documents using a JSON containment query (PostgreSQL only)
*
* @param tableName The name of the table from which documents should be deleted
* @param criteria The object for which JSON containment should be checked
* @throws DocumentException If called on a SQLite connection
*/
def deleteByContains[TContains](tableName: String, criteria: TContains): Unit =
Delete.byContains(tableName, criteria, conn)
/**
* Delete documents using a JSON Path match query (PostgreSQL only)
*
* @param tableName The name of the table from which documents should be deleted
* @param path The JSON path comparison to match
* @throws DocumentException If called on a SQLite connection
*/
def deleteByJsonPath(tableName: String, path: String): Unit =
Delete.byJsonPath(tableName, path, conn)

View File

@@ -0,0 +1,126 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{DisplayName, Test}
import solutions.bitbadger.documents.{AutoId, DocumentException}
@DisplayName("Scala | AutoId")
class AutoIdTest:
@Test
@DisplayName("Generates a UUID string")
def generateUUID(): Unit =
assertEquals(32, AutoId.generateUUID().length(), "The UUID should have been a 32-character string")
@Test
@DisplayName("Generates a random hex character string of an even length")
def generateRandomStringEven(): Unit =
val result = AutoId.generateRandomString(8)
assertEquals(8, result.length(), s"There should have been 8 characters in $result")
@Test
@DisplayName("Generates a random hex character string of an odd length")
def generateRandomStringOdd(): Unit =
val result = AutoId.generateRandomString(11)
assertEquals(11, result.length(), s"There should have been 11 characters in $result")
@Test
@DisplayName("Generates different random hex character strings")
def generateRandomStringIsRandom(): Unit =
val result1 = AutoId.generateRandomString(16)
val result2 = AutoId.generateRandomString(16)
assertNotEquals(result1, result2, "There should have been 2 different strings generated")
@Test
@DisplayName("needsAutoId fails for null document")
def needsAutoIdFailsForNullDocument(): Unit =
assertThrows(classOf[DocumentException], () => AutoId.needsAutoId(AutoId.DISABLED, null, "id"))
@Test
@DisplayName("needsAutoId fails for missing ID property")
def needsAutoIdFailsForMissingId(): Unit =
assertThrows(classOf[DocumentException], () => AutoId.needsAutoId(AutoId.UUID, IntIdClass(0), "Id"))
@Test
@DisplayName("needsAutoId returns false if disabled")
def needsAutoIdFalseIfDisabled(): Unit =
assertFalse(AutoId.needsAutoId(AutoId.DISABLED, "", ""), "Disabled Auto ID should always return false")
@Test
@DisplayName("needsAutoId returns true for Number strategy and byte ID of 0")
def needsAutoIdTrueForByteWithZero(): Unit =
assertTrue(AutoId.needsAutoId(AutoId.NUMBER, ByteIdClass(0), "id"), "Number Auto ID with 0 should return true")
@Test
@DisplayName("needsAutoId returns false for Number strategy and byte ID of non-0")
def needsAutoIdFalseForByteWithNonZero(): Unit =
assertFalse(AutoId.needsAutoId(AutoId.NUMBER, ByteIdClass(77), "id"), "Number Auto ID with 77 should return false")
@Test
@DisplayName("needsAutoId returns true for Number strategy and short ID of 0")
def needsAutoIdTrueForShortWithZero(): Unit =
assertTrue(AutoId.needsAutoId(AutoId.NUMBER, ShortIdClass(0), "id"), "Number Auto ID with 0 should return true")
@Test
@DisplayName("needsAutoId returns false for Number strategy and short ID of non-0")
def needsAutoIdFalseForShortWithNonZero(): Unit =
assertFalse(AutoId.needsAutoId(AutoId.NUMBER, ShortIdClass(31), "id"), "Number Auto ID with 31 should return false")
@Test
@DisplayName("needsAutoId returns true for Number strategy and int ID of 0")
def needsAutoIdTrueForIntWithZero(): Unit =
assertTrue(AutoId.needsAutoId(AutoId.NUMBER, IntIdClass(0), "id"), "Number Auto ID with 0 should return true")
@Test
@DisplayName("needsAutoId returns false for Number strategy and int ID of non-0")
def needsAutoIdFalseForIntWithNonZero(): Unit =
assertFalse(AutoId.needsAutoId(AutoId.NUMBER, IntIdClass(6), "id"), "Number Auto ID with 6 should return false")
@Test
@DisplayName("needsAutoId returns true for Number strategy and long ID of 0")
def needsAutoIdTrueForLongWithZero(): Unit =
assertTrue(AutoId.needsAutoId(AutoId.NUMBER, LongIdClass(0), "id"), "Number Auto ID with 0 should return true")
@Test
@DisplayName("needsAutoId returns false for Number strategy and long ID of non-0")
def needsAutoIdFalseForLongWithNonZero(): Unit =
assertFalse(AutoId.needsAutoId(AutoId.NUMBER, LongIdClass(2), "id"), "Number Auto ID with 2 should return false")
@Test
@DisplayName("needsAutoId fails for Number strategy and non-number ID")
def needsAutoIdFailsForNumberWithStringId(): Unit =
assertThrows(classOf[DocumentException], () => AutoId.needsAutoId(AutoId.NUMBER, StringIdClass(""), "id"))
@Test
@DisplayName("needsAutoId returns true for UUID strategy and blank ID")
def needsAutoIdTrueForUUIDWithBlank(): Unit =
assertTrue(AutoId.needsAutoId(AutoId.UUID, StringIdClass(""), "id"), "UUID Auto ID with blank should return true")
@Test
@DisplayName("needsAutoId returns false for UUID strategy and non-blank ID")
def needsAutoIdFalseForUUIDNotBlank(): Unit =
assertFalse(AutoId.needsAutoId(AutoId.UUID, StringIdClass("howdy"), "id"),
"UUID Auto ID with non-blank should return false")
@Test
@DisplayName("needsAutoId fails for UUID strategy and non-string ID")
def needsAutoIdFailsForUUIDNonString(): Unit =
assertThrows(classOf[DocumentException], () => AutoId.needsAutoId(AutoId.UUID, IntIdClass(5), "id"))
@Test
@DisplayName("needsAutoId returns true for Random String strategy and blank ID")
def needsAutoIdTrueForRandomWithBlank(): Unit =
assertTrue(AutoId.needsAutoId(AutoId.RANDOM_STRING, StringIdClass(""), "id"),
"Random String Auto ID with blank should return true")
@Test
@DisplayName("needsAutoId returns false for Random String strategy and non-blank ID")
def needsAutoIdFalseForRandomNotBlank(): Unit =
assertFalse(AutoId.needsAutoId(AutoId.RANDOM_STRING, StringIdClass("full"), "id"),
"Random String Auto ID with non-blank should return false")
@Test
@DisplayName("needsAutoId fails for Random String strategy and non-string ID")
def needsAutoIdFailsForRandomNonString(): Unit =
assertThrows(classOf[DocumentException], () => AutoId.needsAutoId(AutoId.RANDOM_STRING, ShortIdClass(55), "id"))

View File

@@ -0,0 +1,3 @@
package solutions.bitbadger.documents.scala.tests
class ByteIdClass(var id: Byte)

View File

@@ -0,0 +1,34 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{DisplayName, Test}
import solutions.bitbadger.documents.{AutoId, Configuration, Dialect, DocumentException}
@DisplayName("Scala | Configuration")
class ConfigurationTest:
@Test
@DisplayName("Default ID field is `id`")
def defaultIdField(): Unit =
assertEquals("id", Configuration.idField, "Default ID field incorrect")
@Test
@DisplayName("Default Auto ID strategy is `DISABLED`")
def defaultAutoId(): Unit =
assertEquals(AutoId.DISABLED, Configuration.autoIdStrategy, "Default Auto ID strategy should be `disabled`")
@Test
@DisplayName("Default ID string length should be 16")
def defaultIdStringLength(): Unit =
assertEquals(16, Configuration.idStringLength, "Default ID string length should be 16")
@Test
@DisplayName("Dialect is derived from connection string")
def dialectIsDerived(): Unit =
try {
assertThrows(classOf[DocumentException], () => Configuration.dialect())
Configuration.setConnectionString("jdbc:postgresql:db")
assertEquals(Dialect.POSTGRESQL, Configuration.dialect())
} finally {
Configuration.setConnectionString(null)
}

View File

@@ -0,0 +1,66 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{AfterEach, DisplayName, Test}
import solutions.bitbadger.documents.{DocumentException, Field}
import solutions.bitbadger.documents.query.CountQuery
import scala.jdk.CollectionConverters.*
@DisplayName("Scala | Query | CountQuery")
class CountQueryTest:
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
def cleanUp(): Unit =
ForceDialect.none()
@Test
@DisplayName("all generates correctly")
def all(): Unit =
assertEquals(s"SELECT COUNT(*) AS it FROM $TEST_TABLE", CountQuery.all(TEST_TABLE),
"Count query not constructed correctly")
@Test
@DisplayName("byFields generates correctly | PostgreSQL")
def byFieldsPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE data->>'test' = :field0",
CountQuery.byFields(TEST_TABLE, List(Field.equal("test", "", ":field0")).asJava),
"Count query not constructed correctly")
@Test
@DisplayName("byFields generates correctly | SQLite")
def byFieldsSQLite(): Unit =
ForceDialect.sqlite()
assertEquals(s"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE data->>'test' = :field0",
CountQuery.byFields(TEST_TABLE, List(Field.equal("test", "", ":field0")).asJava),
"Count query not constructed correctly")
@Test
@DisplayName("byContains generates correctly | PostgreSQL")
def byContainsPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE data @> :criteria", CountQuery.byContains(TEST_TABLE),
"Count query not constructed correctly")
@Test
@DisplayName("byContains fails | SQLite")
def byContainsSQLite(): Unit =
ForceDialect.sqlite()
assertThrows(classOf[DocumentException], () => CountQuery.byContains(TEST_TABLE))
@Test
@DisplayName("byJsonPath generates correctly | PostgreSQL")
def byJsonPathPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"SELECT COUNT(*) AS it FROM $TEST_TABLE WHERE jsonb_path_exists(data, :path::jsonpath)",
CountQuery.byJsonPath(TEST_TABLE), "Count query not constructed correctly")
@Test
@DisplayName("byJsonPath fails | SQLite")
def byJsonPathSQLite(): Unit =
ForceDialect.sqlite()
assertThrows(classOf[DocumentException], () => CountQuery.byJsonPath(TEST_TABLE))

View File

@@ -0,0 +1,104 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{AfterEach, DisplayName, Test}
import solutions.bitbadger.documents.{Dialect, DocumentException, DocumentIndex}
import solutions.bitbadger.documents.query.DefinitionQuery
import scala.jdk.CollectionConverters.*
@DisplayName("Scala | Query | DefinitionQuery")
class DefinitionQueryTest:
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
def cleanUp(): Unit =
ForceDialect.none()
@Test
@DisplayName("ensureTableFor generates correctly")
def ensureTableFor(): Unit =
assertEquals("CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)",
DefinitionQuery.ensureTableFor("my.table", "JSONB"), "CREATE TABLE statement not constructed correctly")
@Test
@DisplayName("ensureTable generates correctly | PostgreSQL")
def ensureTablePostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"CREATE TABLE IF NOT EXISTS $TEST_TABLE (data JSONB NOT NULL)",
DefinitionQuery.ensureTable(TEST_TABLE))
@Test
@DisplayName("ensureTable generates correctly | SQLite")
def ensureTableSQLite(): Unit =
ForceDialect.sqlite()
assertEquals(s"CREATE TABLE IF NOT EXISTS $TEST_TABLE (data TEXT NOT NULL)",
DefinitionQuery.ensureTable(TEST_TABLE))
@Test
@DisplayName("ensureTable fails when no dialect is set")
def ensureTableFailsUnknown(): Unit =
assertThrows(classOf[DocumentException], () => DefinitionQuery.ensureTable(TEST_TABLE))
@Test
@DisplayName("ensureKey generates correctly with schema")
def ensureKeyWithSchema(): Unit =
assertEquals("CREATE UNIQUE INDEX IF NOT EXISTS idx_table_key ON test.table ((data->>'id'))",
DefinitionQuery.ensureKey("test.table", Dialect.POSTGRESQL),
"CREATE INDEX for key statement with schema not constructed correctly")
@Test
@DisplayName("ensureKey generates correctly without schema")
def ensureKeyWithoutSchema(): Unit =
assertEquals(s"CREATE UNIQUE INDEX IF NOT EXISTS idx_${TEST_TABLE}_key ON $TEST_TABLE ((data->>'id'))",
DefinitionQuery.ensureKey(TEST_TABLE, Dialect.SQLITE),
"CREATE INDEX for key statement without schema not constructed correctly")
@Test
@DisplayName("ensureIndexOn generates multiple fields and directions")
def ensureIndexOnMultipleFields(): Unit =
assertEquals("CREATE INDEX IF NOT EXISTS idx_table_gibberish ON test.table " +
"((data->>'taco'), (data->>'guac') DESC, (data->>'salsa') ASC)",
DefinitionQuery.ensureIndexOn("test.table", "gibberish", List("taco", "guac DESC", "salsa ASC").asJava,
Dialect.POSTGRESQL),
"CREATE INDEX for multiple field statement not constructed correctly")
@Test
@DisplayName("ensureIndexOn generates nested field | PostgreSQL")
def ensureIndexOnNestedPostgres(): Unit =
assertEquals(s"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_nest ON $TEST_TABLE ((data#>>'{a,b,c}'))",
DefinitionQuery.ensureIndexOn(TEST_TABLE, "nest", List("a.b.c").asJava, Dialect.POSTGRESQL),
"CREATE INDEX for nested PostgreSQL field incorrect")
@Test
@DisplayName("ensureIndexOn generates nested field | SQLite")
def ensureIndexOnNestedSQLite(): Unit =
assertEquals(s"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_nest ON $TEST_TABLE ((data->'a'->'b'->>'c'))",
DefinitionQuery.ensureIndexOn(TEST_TABLE, "nest", List("a.b.c").asJava, Dialect.SQLITE),
"CREATE INDEX for nested SQLite field incorrect")
@Test
@DisplayName("ensureDocumentIndexOn generates Full | PostgreSQL")
def ensureDocumentIndexOnFullPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_document ON $TEST_TABLE USING GIN (data)",
DefinitionQuery.ensureDocumentIndexOn(TEST_TABLE, DocumentIndex.FULL),
"CREATE INDEX for full document index incorrect")
@Test
@DisplayName("ensureDocumentIndexOn generates Optimized | PostgreSQL")
def ensureDocumentIndexOnOptimizedPostgres(): Unit =
ForceDialect.postgres()
assertEquals(
s"CREATE INDEX IF NOT EXISTS idx_${TEST_TABLE}_document ON $TEST_TABLE USING GIN (data jsonb_path_ops)",
DefinitionQuery.ensureDocumentIndexOn(TEST_TABLE, DocumentIndex.OPTIMIZED),
"CREATE INDEX for optimized document index incorrect")
@Test
@DisplayName("ensureDocumentIndexOn fails | SQLite")
def ensureDocumentIndexOnFailsSQLite(): Unit =
ForceDialect.sqlite()
assertThrows(classOf[DocumentException],
() => DefinitionQuery.ensureDocumentIndexOn(TEST_TABLE, DocumentIndex.FULL))

View File

@@ -0,0 +1,74 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{AfterEach, DisplayName, Test}
import solutions.bitbadger.documents.{DocumentException, Field}
import solutions.bitbadger.documents.query.DeleteQuery
import scala.jdk.CollectionConverters.*
@DisplayName("Scala | Query | DeleteQuery")
class DeleteQueryTest:
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
def cleanUp(): Unit =
ForceDialect.none()
@Test
@DisplayName("byId generates correctly | PostgreSQL")
def byIdPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"DELETE FROM $TEST_TABLE WHERE data->>'id' = :id", DeleteQuery.byId(TEST_TABLE),
"Delete query not constructed correctly")
@Test
@DisplayName("byId generates correctly | SQLite")
def byIdSQLite(): Unit =
ForceDialect.sqlite()
assertEquals(s"DELETE FROM $TEST_TABLE WHERE data->>'id' = :id", DeleteQuery.byId(TEST_TABLE),
"Delete query not constructed correctly")
@Test
@DisplayName("byFields generates correctly | PostgreSQL")
def byFieldsPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"DELETE FROM $TEST_TABLE WHERE data->>'a' = :b",
DeleteQuery.byFields(TEST_TABLE, List(Field.equal("a", "", ":b")).asJava),
"Delete query not constructed correctly")
@Test
@DisplayName("byFields generates correctly | SQLite")
def byFieldsSQLite(): Unit =
ForceDialect.sqlite()
assertEquals(s"DELETE FROM $TEST_TABLE WHERE data->>'a' = :b",
DeleteQuery.byFields(TEST_TABLE, List(Field.equal("a", "", ":b")).asJava),
"Delete query not constructed correctly")
@Test
@DisplayName("byContains generates correctly | PostgreSQL")
def byContainsPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"DELETE FROM $TEST_TABLE WHERE data @> :criteria", DeleteQuery.byContains(TEST_TABLE),
"Delete query not constructed correctly")
@Test
@DisplayName("byContains fails | SQLite")
def byContainsSQLite(): Unit =
ForceDialect.sqlite()
assertThrows(classOf[DocumentException], () => DeleteQuery.byContains(TEST_TABLE))
@Test
@DisplayName("byJsonPath generates correctly | PostgreSQL")
def byJsonPathPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"DELETE FROM $TEST_TABLE WHERE jsonb_path_exists(data, :path::jsonpath)",
DeleteQuery.byJsonPath(TEST_TABLE), "Delete query not constructed correctly")
@Test
@DisplayName("byJsonPath fails | SQLite")
def byJsonPathSQLite(): Unit =
ForceDialect.sqlite()
assertThrows(classOf[DocumentException], () => DeleteQuery.byJsonPath(TEST_TABLE))

View File

@@ -0,0 +1,34 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{DisplayName, Test}
import solutions.bitbadger.documents.{Dialect, DocumentException}
@DisplayName("Scala | Dialect")
class DialectTest:
@Test
@DisplayName("deriveFromConnectionString derives PostgreSQL correctly")
def derivesPostgres(): Unit =
assertEquals(Dialect.POSTGRESQL, Dialect.deriveFromConnectionString("jdbc:postgresql:db"),
"Dialect should have been PostgreSQL")
@Test
@DisplayName("deriveFromConnectionString derives SQLite correctly")
def derivesSQLite(): Unit =
assertEquals(Dialect.SQLITE, Dialect.deriveFromConnectionString("jdbc:sqlite:memory"),
"Dialect should have been SQLite")
@Test
@DisplayName("deriveFromConnectionString fails when the connection string is unknown")
def deriveFailsWhenUnknown(): Unit =
try {
Dialect.deriveFromConnectionString("SQL Server")
fail("Dialect derivation should have failed")
} catch {
case ex: DocumentException =>
assertNotNull(ex.getMessage, "The exception message should not have been null")
assertTrue(ex.getMessage.contains("[SQL Server]"),
"The connection string should have been in the exception message")
}

View File

@@ -0,0 +1,18 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{DisplayName, Test}
import solutions.bitbadger.documents.DocumentIndex
@DisplayName("Scala | DocumentIndex")
class DocumentIndexTest:
@Test
@DisplayName("FULL uses proper SQL")
def fullSQL(): Unit =
assertEquals("", DocumentIndex.FULL.getSql, "The SQL for Full is incorrect")
@Test
@DisplayName("OPTIMIZED uses proper SQL")
def optimizedSQL(): Unit =
assertEquals(" jsonb_path_ops", DocumentIndex.OPTIMIZED.getSql, "The SQL for Optimized is incorrect")

View File

@@ -0,0 +1,110 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{AfterEach, DisplayName, Test}
import solutions.bitbadger.documents.{AutoId, Configuration, DocumentException}
import solutions.bitbadger.documents.query.DocumentQuery
@DisplayName("Scala | Query | DocumentQuery")
class DocumentQueryTest:
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
def cleanUp(): Unit =
ForceDialect.none()
@Test
@DisplayName("insert generates no auto ID | PostgreSQL")
def insertNoAutoPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"INSERT INTO $TEST_TABLE VALUES (:data)", DocumentQuery.insert(TEST_TABLE))
@Test
@DisplayName("insert generates no auto ID | SQLite")
def insertNoAutoSQLite(): Unit =
ForceDialect.sqlite()
assertEquals(s"INSERT INTO $TEST_TABLE VALUES (:data)", DocumentQuery.insert(TEST_TABLE))
@Test
@DisplayName("insert generates auto number | PostgreSQL")
def insertAutoNumberPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"INSERT INTO $TEST_TABLE VALUES (:data::jsonb || ('{\"id\":' " +
s"|| (SELECT COALESCE(MAX((data->>'id')::numeric), 0) + 1 FROM $TEST_TABLE) || '}')::jsonb)",
DocumentQuery.insert(TEST_TABLE, AutoId.NUMBER))
@Test
@DisplayName("insert generates auto number | SQLite")
def insertAutoNumberSQLite(): Unit =
ForceDialect.sqlite()
assertEquals(s"INSERT INTO $TEST_TABLE VALUES (json_set(:data, '$$.id', " +
s"(SELECT coalesce(max(data->>'id'), 0) + 1 FROM $TEST_TABLE)))",
DocumentQuery.insert(TEST_TABLE, AutoId.NUMBER))
@Test
@DisplayName("insert generates auto UUID | PostgreSQL")
def insertAutoUUIDPostgres(): Unit =
ForceDialect.postgres()
val query = DocumentQuery.insert(TEST_TABLE, AutoId.UUID)
assertTrue(query.startsWith(s"INSERT INTO $TEST_TABLE VALUES (:data::jsonb || '{\"id\":\""),
s"Query start not correct (actual: $query)")
assertTrue(query.endsWith("\"}')"), "Query end not correct")
@Test
@DisplayName("insert generates auto UUID | SQLite")
def insertAutoUUIDSQLite(): Unit =
ForceDialect.sqlite()
val query = DocumentQuery.insert(TEST_TABLE, AutoId.UUID)
assertTrue(query.startsWith(s"INSERT INTO $TEST_TABLE VALUES (json_set(:data, '$$.id', '"),
s"Query start not correct (actual: $query)")
assertTrue(query.endsWith("'))"), "Query end not correct")
@Test
@DisplayName("insert generates auto random string | PostgreSQL")
def insertAutoRandomPostgres(): Unit =
try {
ForceDialect.postgres()
Configuration.idStringLength = 8
val query = DocumentQuery.insert(TEST_TABLE, AutoId.RANDOM_STRING)
assertTrue(query.startsWith(s"INSERT INTO $TEST_TABLE VALUES (:data::jsonb || '{\"id\":\""),
s"Query start not correct (actual: $query)")
assertTrue(query.endsWith("\"}')"), "Query end not correct")
assertEquals(8, query.replace(s"INSERT INTO $TEST_TABLE VALUES (:data::jsonb || '{\"id\":\"", "")
.replace("\"}')", "").length,
"Random string length incorrect")
} finally {
Configuration.idStringLength = 16
}
@Test
@DisplayName("insert generates auto random string | SQLite")
def insertAutoRandomSQLite(): Unit =
ForceDialect.sqlite()
val query = DocumentQuery.insert(TEST_TABLE, AutoId.RANDOM_STRING)
assertTrue(query.startsWith(s"INSERT INTO $TEST_TABLE VALUES (json_set(:data, '$$.id', '"),
s"Query start not correct (actual: $query)")
assertTrue(query.endsWith("'))"), "Query end not correct")
assertEquals(Configuration.idStringLength,
query.replace(s"INSERT INTO $TEST_TABLE VALUES (json_set(:data, '$$.id', '", "").replace("'))", "").length,
"Random string length incorrect")
@Test
@DisplayName("insert fails when no dialect is set")
def insertFailsUnknown(): Unit =
assertThrows(classOf[DocumentException], () => DocumentQuery.insert(TEST_TABLE))
@Test
@DisplayName("save generates correctly")
def save(): Unit =
ForceDialect.postgres()
assertEquals(
s"INSERT INTO $TEST_TABLE VALUES (:data) ON CONFLICT ((data->>'id')) DO UPDATE SET data = EXCLUDED.data",
DocumentQuery.save(TEST_TABLE), "INSERT ON CONFLICT UPDATE statement not constructed correctly")
@Test
@DisplayName("update generates successfully")
def update(): Unit =
assertEquals(s"UPDATE $TEST_TABLE SET data = :data", DocumentQuery.update(TEST_TABLE),
"Update query not constructed correctly")

View File

@@ -0,0 +1,74 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{AfterEach, DisplayName, Test}
import solutions.bitbadger.documents.{DocumentException, Field}
import solutions.bitbadger.documents.query.ExistsQuery
import scala.jdk.CollectionConverters.*
@DisplayName("Scala | Query | ExistsQuery")
class ExistsQueryTest:
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
def cleanUp(): Unit =
ForceDialect.none()
@Test
@DisplayName("byId generates correctly | PostgreSQL")
def byIdPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE data->>'id' = :id) AS it",
ExistsQuery.byId(TEST_TABLE), "Exists query not constructed correctly")
@Test
@DisplayName("byId generates correctly | SQLite")
def byIdSQLite(): Unit =
ForceDialect.sqlite()
assertEquals(s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE data->>'id' = :id) AS it",
ExistsQuery.byId(TEST_TABLE), "Exists query not constructed correctly")
@Test
@DisplayName("byFields generates correctly | PostgreSQL")
def byFieldsPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE (data->>'it')::numeric = :test) AS it",
ExistsQuery.byFields(TEST_TABLE, List(Field.equal("it", 7, ":test")).asJava),
"Exists query not constructed correctly")
@Test
@DisplayName("byFields generates correctly | SQLite")
def byFieldsSQLite(): Unit =
ForceDialect.sqlite()
assertEquals(s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE data->>'it' = :test) AS it",
ExistsQuery.byFields(TEST_TABLE, List(Field.equal("it", 7, ":test")).asJava),
"Exists query not constructed correctly")
@Test
@DisplayName("byContains generates correctly | PostgreSQL")
def byContainsPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE data @> :criteria) AS it",
ExistsQuery.byContains(TEST_TABLE), "Exists query not constructed correctly")
@Test
@DisplayName("byContains fails | SQLite")
def byContainsSQLite(): Unit =
ForceDialect.sqlite()
assertThrows(classOf[DocumentException], () => ExistsQuery.byContains(TEST_TABLE))
@Test
@DisplayName("byJsonPath generates correctly | PostgreSQL")
def byJsonPathPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"SELECT EXISTS (SELECT 1 FROM $TEST_TABLE WHERE jsonb_path_exists(data, :path::jsonpath)) AS it",
ExistsQuery.byJsonPath(TEST_TABLE), "Exists query not constructed correctly")
@Test
@DisplayName("byJsonPath fails | SQLite")
def byJsonPathSQLite(): Unit =
ForceDialect.sqlite()
assertThrows(classOf[DocumentException], () => ExistsQuery.byJsonPath(TEST_TABLE))

View File

@@ -0,0 +1,21 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{DisplayName, Test}
import solutions.bitbadger.documents.FieldMatch
/**
* Unit tests for the `FieldMatch` enum
*/
@DisplayName("Scala | FieldMatch")
class FieldMatchTest:
@Test
@DisplayName("ANY uses proper SQL")
def any(): Unit =
assertEquals("OR", FieldMatch.ANY.getSql, "ANY should use OR")
@Test
@DisplayName("ALL uses proper SQL")
def all(): Unit =
assertEquals("AND", FieldMatch.ALL.getSql, "ALL should use AND")

View File

@@ -0,0 +1,537 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{AfterEach, DisplayName, Test}
import solutions.bitbadger.documents.*
import _root_.scala.jdk.CollectionConverters.*
@DisplayName("Scala | Field")
class FieldTest:
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
def cleanUp(): Unit =
ForceDialect.none()
// ~~~ INSTANCE METHODS ~~~
@Test
@DisplayName("withParameterName fails for invalid name")
def withParamNameFails(): Unit =
assertThrows(classOf[DocumentException], () => Field.equal("it", "").withParameterName("2424"))
@Test
@DisplayName("withParameterName works with colon prefix")
def withParamNameColon(): Unit =
val field = Field.equal("abc", "22").withQualifier("me")
val withParam = field.withParameterName(":test")
assertNotSame(field, withParam, "A new Field instance should have been created")
assertEquals(field.getName, withParam.getName, "Name should have been preserved")
assertEquals(field.getComparison, withParam.getComparison, "Comparison should have been preserved")
assertEquals(":test", withParam.getParameterName, "Parameter name not set correctly")
assertEquals(field.getQualifier, withParam.getQualifier, "Qualifier should have been preserved")
@Test
@DisplayName("withParameterName works with at-sign prefix")
def withParamNameAtSign(): Unit =
val field = Field.equal("def", "44")
val withParam = field.withParameterName("@unit")
assertNotSame(field, withParam, "A new Field instance should have been created")
assertEquals(field.getName, withParam.getName, "Name should have been preserved")
assertEquals(field.getComparison, withParam.getComparison, "Comparison should have been preserved")
assertEquals("@unit", withParam.getParameterName, "Parameter name not set correctly")
assertEquals(field.getQualifier, withParam.getQualifier, "Qualifier should have been preserved")
@Test
@DisplayName("withQualifier sets qualifier correctly")
def withQualifier(): Unit =
val field = Field.equal("j", "k")
val withQual = field.withQualifier("test")
assertNotSame(field, withQual, "A new Field instance should have been created")
assertEquals(field.getName, withQual.getName, "Name should have been preserved")
assertEquals(field.getComparison, withQual.getComparison, "Comparison should have been preserved")
assertEquals(field.getParameterName, withQual.getParameterName, "Parameter Name should have been preserved")
assertEquals("test", withQual.getQualifier, "Qualifier not set correctly")
@Test
@DisplayName("path generates for simple unqualified PostgreSQL field")
def pathPostgresSimpleUnqualified(): Unit =
assertEquals("data->>'SomethingCool'",
Field.greaterOrEqual("SomethingCool", 18).path(Dialect.POSTGRESQL, FieldFormat.SQL), "Path not correct")
@Test
@DisplayName("path generates for simple qualified PostgreSQL field")
def pathPostgresSimpleQualified(): Unit =
assertEquals("this.data->>'SomethingElse'",
Field.less("SomethingElse", 9).withQualifier("this").path(Dialect.POSTGRESQL, FieldFormat.SQL),
"Path not correct")
@Test
@DisplayName("path generates for nested unqualified PostgreSQL field")
def pathPostgresNestedUnqualified(): Unit =
assertEquals("data#>>'{My,Nested,Field}'",
Field.equal("My.Nested.Field", "howdy").path(Dialect.POSTGRESQL, FieldFormat.SQL), "Path not correct")
@Test
@DisplayName("path generates for nested qualified PostgreSQL field")
def pathPostgresNestedQualified(): Unit =
assertEquals("bird.data#>>'{Nest,Away}'",
Field.equal("Nest.Away", "doc").withQualifier("bird").path(Dialect.POSTGRESQL, FieldFormat.SQL),
"Path not correct")
@Test
@DisplayName("path generates for simple unqualified SQLite field")
def pathSQLiteSimpleUnqualified(): Unit =
assertEquals("data->>'SomethingCool'",
Field.greaterOrEqual("SomethingCool", 18).path(Dialect.SQLITE, FieldFormat.SQL), "Path not correct")
@Test
@DisplayName("path generates for simple qualified SQLite field")
def pathSQLiteSimpleQualified(): Unit =
assertEquals("this.data->>'SomethingElse'",
Field.less("SomethingElse", 9).withQualifier("this").path(Dialect.SQLITE, FieldFormat.SQL),
"Path not correct")
@Test
@DisplayName("path generates for nested unqualified SQLite field")
def pathSQLiteNestedUnqualified(): Unit =
assertEquals("data->'My'->'Nested'->>'Field'",
Field.equal("My.Nested.Field", "howdy").path(Dialect.SQLITE, FieldFormat.SQL), "Path not correct")
@Test
@DisplayName("path generates for nested qualified SQLite field")
def pathSQLiteNestedQualified(): Unit =
assertEquals("bird.data->'Nest'->>'Away'",
Field.equal("Nest.Away", "doc").withQualifier("bird").path(Dialect.SQLITE, FieldFormat.SQL),
"Path not correct")
@Test
@DisplayName("toWhere generates for exists w/o qualifier | PostgreSQL")
def toWhereExistsNoQualPostgres(): Unit =
ForceDialect.postgres()
assertEquals("data->>'that_field' IS NOT NULL", Field.exists("that_field").toWhere,
"Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates for exists w/o qualifier | SQLite")
def toWhereExistsNoQualSQLite(): Unit =
ForceDialect.sqlite()
assertEquals("data->>'that_field' IS NOT NULL", Field.exists("that_field").toWhere,
"Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates for not-exists w/o qualifier | PostgreSQL")
def toWhereNotExistsNoQualPostgres(): Unit =
ForceDialect.postgres()
assertEquals("data->>'a_field' IS NULL", Field.notExists("a_field").toWhere,
"Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates for not-exists w/o qualifier | SQLite")
def toWhereNotExistsNoQualSQLite(): Unit =
ForceDialect.sqlite()
assertEquals("data->>'a_field' IS NULL", Field.notExists("a_field").toWhere,
"Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates for BETWEEN w/o qualifier, numeric range | PostgreSQL")
def toWhereBetweenNoQualNumericPostgres(): Unit =
ForceDialect.postgres()
assertEquals("(data->>'age')::numeric BETWEEN @agemin AND @agemax",
Field.between("age", 13, 17, "@age").toWhere, "Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates for BETWEEN w/o qualifier, alphanumeric range | PostgreSQL")
def toWhereBetweenNoQualAlphaPostgres(): Unit =
ForceDialect.postgres()
assertEquals("data->>'city' BETWEEN :citymin AND :citymax",
Field.between("city", "Atlanta", "Chicago", ":city").toWhere, "Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates for BETWEEN w/o qualifier | SQLite")
def toWhereBetweenNoQualSQLite(): Unit =
ForceDialect.sqlite()
assertEquals("data->>'age' BETWEEN @agemin AND @agemax", Field.between("age", 13, 17, "@age").toWhere,
"Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates for BETWEEN w/ qualifier, numeric range | PostgreSQL")
def toWhereBetweenQualNumericPostgres(): Unit =
ForceDialect.postgres()
assertEquals("(test.data->>'age')::numeric BETWEEN @agemin AND @agemax",
Field.between("age", 13, 17, "@age").withQualifier("test").toWhere, "Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates for BETWEEN w/ qualifier, alphanumeric range | PostgreSQL")
def toWhereBetweenQualAlphaPostgres(): Unit =
ForceDialect.postgres()
assertEquals("unit.data->>'city' BETWEEN :citymin AND :citymax",
Field.between("city", "Atlanta", "Chicago", ":city").withQualifier("unit").toWhere,
"Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates for BETWEEN w/ qualifier | SQLite")
def toWhereBetweenQualSQLite(): Unit =
ForceDialect.sqlite()
assertEquals("my.data->>'age' BETWEEN @agemin AND @agemax",
Field.between("age", 13, 17, "@age").withQualifier("my").toWhere, "Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates for IN/any, numeric values | PostgreSQL")
def toWhereAnyNumericPostgres(): Unit =
ForceDialect.postgres()
assertEquals("(data->>'even')::numeric IN (:nbr_0, :nbr_1, :nbr_2)",
Field.any("even", List(2, 4, 6).asJava, ":nbr").toWhere, "Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates for IN/any, alphanumeric values | PostgreSQL")
def toWhereAnyAlphaPostgres(): Unit =
ForceDialect.postgres()
assertEquals("data->>'test' IN (:city_0, :city_1)",
Field.any("test", List("Atlanta", "Chicago").asJava, ":city").toWhere,
"Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates for IN/any | SQLite")
def toWhereAnySQLite(): Unit =
ForceDialect.sqlite()
assertEquals("data->>'test' IN (:city_0, :city_1)",
Field.any("test", List("Atlanta", "Chicago").asJava, ":city").toWhere,
"Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates for inArray | PostgreSQL")
def toWhereInArrayPostgres(): Unit =
ForceDialect.postgres()
assertEquals("data->'even' ??| ARRAY[:it_0, :it_1, :it_2, :it_3]",
Field.inArray("even", "tbl", List(2, 4, 6, 8).asJava, ":it").toWhere,
"Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates for inArray | SQLite")
def toWhereInArraySQLite(): Unit =
ForceDialect.sqlite()
assertEquals("EXISTS (SELECT 1 FROM json_each(tbl.data, '$.test') WHERE value IN (:city_0, :city_1))",
Field.inArray("test", "tbl", List("Atlanta", "Chicago").asJava, ":city").toWhere,
"Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates for others w/o qualifier | PostgreSQL")
def toWhereOtherNoQualPostgres(): Unit =
ForceDialect.postgres()
assertEquals("data->>'some_field' = :value", Field.equal("some_field", "", ":value").toWhere,
"Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates for others w/o qualifier | SQLite")
def toWhereOtherNoQualSQLite(): Unit =
ForceDialect.sqlite()
assertEquals("data->>'some_field' = :value", Field.equal("some_field", "", ":value").toWhere,
"Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates no-parameter w/ qualifier | PostgreSQL")
def toWhereNoParamWithQualPostgres(): Unit =
ForceDialect.postgres()
assertEquals("test.data->>'no_field' IS NOT NULL", Field.exists("no_field").withQualifier("test").toWhere,
"Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates no-parameter w/ qualifier | SQLite")
def toWhereNoParamWithQualSQLite(): Unit =
ForceDialect.sqlite()
assertEquals("test.data->>'no_field' IS NOT NULL", Field.exists("no_field").withQualifier("test").toWhere,
"Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates parameter w/ qualifier | PostgreSQL")
def toWhereParamWithQualPostgres(): Unit =
ForceDialect.postgres()
assertEquals("(q.data->>'le_field')::numeric <= :it",
Field.lessOrEqual("le_field", 18, ":it").withQualifier("q").toWhere, "Field WHERE clause not generated correctly")
@Test
@DisplayName("toWhere generates parameter w/ qualifier | SQLite")
def toWhereParamWithQualSQLite(): Unit =
ForceDialect.sqlite()
assertEquals("q.data->>'le_field' <= :it",
Field.lessOrEqual("le_field", 18, ":it").withQualifier("q").toWhere,
"Field WHERE clause not generated correctly")
// ~~~ STATIC CONSTRUCTOR TESTS ~~~
@Test
@DisplayName("equal constructs a field w/o parameter name")
def equalCtor(): Unit =
val field = Field.equal("Test", 14)
assertEquals("Test", field.getName, "Field name not filled correctly")
assertEquals(Op.EQUAL, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals(14, field.getComparison.getValue, "Field comparison value not filled correctly")
assertNull(field.getParameterName, "The parameter name should have been null")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("equal constructs a field w/ parameter name")
def equalParameterCtor(): Unit =
val field = Field.equal("Test", 14, ":w")
assertEquals("Test", field.getName, "Field name not filled correctly")
assertEquals(Op.EQUAL, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals(14, field.getComparison.getValue, "Field comparison value not filled correctly")
assertEquals(":w", field.getParameterName, "Field parameter name not filled correctly")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("greater constructs a field w/o parameter name")
def greaterCtor(): Unit =
val field = Field.greater("Great", "night")
assertEquals("Great", field.getName, "Field name not filled correctly")
assertEquals(Op.GREATER, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals("night", field.getComparison.getValue, "Field comparison value not filled correctly")
assertNull(field.getParameterName, "The parameter name should have been null")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("greater constructs a field w/ parameter name")
def greaterParameterCtor(): Unit =
val field = Field.greater("Great", "night", ":yeah")
assertEquals("Great", field.getName, "Field name not filled correctly")
assertEquals(Op.GREATER, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals("night", field.getComparison.getValue, "Field comparison value not filled correctly")
assertEquals(":yeah", field.getParameterName, "Field parameter name not filled correctly")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("greaterOrEqual constructs a field w/o parameter name")
def greaterOrEqualCtor(): Unit =
val field = Field.greaterOrEqual("Nice", 88L)
assertEquals("Nice", field.getName, "Field name not filled correctly")
assertEquals(Op.GREATER_OR_EQUAL, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals(88L, field.getComparison.getValue, "Field comparison value not filled correctly")
assertNull(field.getParameterName, "The parameter name should have been null")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("greaterOrEqual constructs a field w/ parameter name")
def greaterOrEqualParameterCtor(): Unit =
val field = Field.greaterOrEqual("Nice", 88L, ":nice")
assertEquals("Nice", field.getName, "Field name not filled correctly")
assertEquals(Op.GREATER_OR_EQUAL, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals(88L, field.getComparison.getValue, "Field comparison value not filled correctly")
assertEquals(":nice", field.getParameterName, "Field parameter name not filled correctly")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("less constructs a field w/o parameter name")
def lessCtor(): Unit =
val field = Field.less("Lesser", "seven")
assertEquals("Lesser", field.getName, "Field name not filled correctly")
assertEquals(Op.LESS, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals("seven", field.getComparison.getValue, "Field comparison value not filled correctly")
assertNull(field.getParameterName, "The parameter name should have been null")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("less constructs a field w/ parameter name")
def lessParameterCtor(): Unit =
val field = Field.less("Lesser", "seven", ":max")
assertEquals("Lesser", field.getName, "Field name not filled correctly")
assertEquals(Op.LESS, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals("seven", field.getComparison.getValue, "Field comparison value not filled correctly")
assertEquals(":max", field.getParameterName, "Field parameter name not filled correctly")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("lessOrEqual constructs a field w/o parameter name")
def lessOrEqualCtor(): Unit =
val field = Field.lessOrEqual("Nobody", "KNOWS")
assertEquals("Nobody", field.getName, "Field name not filled correctly")
assertEquals(Op.LESS_OR_EQUAL, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals("KNOWS", field.getComparison.getValue, "Field comparison value not filled correctly")
assertNull(field.getParameterName, "The parameter name should have been null")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("lessOrEqual constructs a field w/ parameter name")
def lessOrEqualParameterCtor(): Unit =
val field = Field.lessOrEqual("Nobody", "KNOWS", ":nope")
assertEquals("Nobody", field.getName, "Field name not filled correctly")
assertEquals(Op.LESS_OR_EQUAL, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals("KNOWS", field.getComparison.getValue, "Field comparison value not filled correctly")
assertEquals(":nope", field.getParameterName, "Field parameter name not filled correctly")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("notEqual constructs a field w/o parameter name")
def notEqualCtor(): Unit =
val field = Field.notEqual("Park", "here")
assertEquals("Park", field.getName, "Field name not filled correctly")
assertEquals(Op.NOT_EQUAL, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals("here", field.getComparison.getValue, "Field comparison value not filled correctly")
assertNull(field.getParameterName, "The parameter name should have been null")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("notEqual constructs a field w/ parameter name")
def notEqualParameterCtor(): Unit =
val field = Field.notEqual("Park", "here", ":now")
assertEquals("Park", field.getName, "Field name not filled correctly")
assertEquals(Op.NOT_EQUAL, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals("here", field.getComparison.getValue, "Field comparison value not filled correctly")
assertEquals(":now", field.getParameterName, "Field parameter name not filled correctly")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("between constructs a field w/o parameter name")
def betweenCtor(): Unit =
val field = Field.between("Age", 18, 49)
assertEquals("Age", field.getName, "Field name not filled correctly")
assertEquals(Op.BETWEEN, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals(18, field.getComparison.getValue.getFirst, "Field comparison min value not filled correctly")
assertEquals(49, field.getComparison.getValue.getSecond, "Field comparison max value not filled correctly")
assertNull(field.getParameterName, "The parameter name should have been null")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("between constructs a field w/ parameter name")
def betweenParameterCtor(): Unit =
val field = Field.between("Age", 18, 49, ":limit")
assertEquals("Age", field.getName, "Field name not filled correctly")
assertEquals(Op.BETWEEN, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals(18, field.getComparison.getValue.getFirst, "Field comparison min value not filled correctly")
assertEquals(49, field.getComparison.getValue.getSecond, "Field comparison max value not filled correctly")
assertEquals(":limit", field.getParameterName, "Field parameter name not filled correctly")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("any constructs a field w/o parameter name")
def anyCtor(): Unit =
val field = Field.any("Here", List(8, 16, 32).asJava)
assertEquals("Here", field.getName, "Field name not filled correctly")
assertEquals(Op.IN, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals(List(8, 16, 32).asJava, field.getComparison.getValue, "Field comparison value not filled correctly")
assertNull(field.getParameterName, "The parameter name should have been null")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("any constructs a field w/ parameter name")
def anyParameterCtor(): Unit =
val field = Field.any("Here", List(8, 16, 32).asJava, ":list")
assertEquals("Here", field.getName, "Field name not filled correctly")
assertEquals(Op.IN, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals(List(8, 16, 32).asJava, field.getComparison.getValue, "Field comparison value not filled correctly")
assertEquals(":list", field.getParameterName, "Field parameter name not filled correctly")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("inArray constructs a field w/o parameter name")
def inArrayCtor(): Unit =
val field = Field.inArray("ArrayField", "table", List("z").asJava)
assertEquals("ArrayField", field.getName, "Field name not filled correctly")
assertEquals(Op.IN_ARRAY, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals("table", field.getComparison.getValue.getFirst, "Field comparison table not filled correctly")
assertEquals(List("z").asJava, field.getComparison.getValue.getSecond,
"Field comparison values not filled correctly")
assertNull(field.getParameterName, "The parameter name should have been null")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("inArray constructs a field w/ parameter name")
def inArrayParameterCtor(): Unit =
val field = Field.inArray("ArrayField", "table", List("z").asJava, ":a")
assertEquals("ArrayField", field.getName, "Field name not filled correctly")
assertEquals(Op.IN_ARRAY, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals("table", field.getComparison.getValue.getFirst, "Field comparison table not filled correctly")
assertEquals(List("z").asJava, field.getComparison.getValue.getSecond,
"Field comparison values not filled correctly")
assertEquals(":a", field.getParameterName, "Field parameter name not filled correctly")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("exists constructs a field")
def existsCtor(): Unit =
val field = Field.exists("Groovy")
assertEquals("Groovy", field.getName, "Field name not filled correctly")
assertEquals(Op.EXISTS, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals("", field.getComparison.getValue, "Field comparison value not filled correctly")
assertNull(field.getParameterName, "The parameter name should have been null")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("notExists constructs a field")
def notExistsCtor(): Unit =
val field = Field.notExists("Groovy")
assertEquals("Groovy", field.getName, "Field name not filled correctly")
assertEquals(Op.NOT_EXISTS, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals("", field.getComparison.getValue, "Field comparison value not filled correctly")
assertNull(field.getParameterName, "The parameter name should have been null")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("named constructs a field")
def namedCtor(): Unit =
val field = Field.named("Tacos")
assertEquals("Tacos", field.getName, "Field name not filled correctly")
assertEquals(Op.EQUAL, field.getComparison.getOp, "Field comparison operation not filled correctly")
assertEquals("", field.getComparison.getValue, "Field comparison value not filled correctly")
assertNull(field.getParameterName, "The parameter name should have been null")
assertNull(field.getQualifier, "The qualifier should have been null")
@Test
@DisplayName("static constructors fail for invalid parameter name")
def staticCtorsFailOnParamName(): Unit =
assertThrows(classOf[DocumentException], () => Field.equal("a", "b", "that ain't it, Jack..."))
@Test
@DisplayName("nameToPath creates a simple PostgreSQL SQL name")
def nameToPathPostgresSimpleSQL(): Unit =
assertEquals("data->>'Simple'", Field.nameToPath("Simple", Dialect.POSTGRESQL, FieldFormat.SQL),
"Path not constructed correctly")
@Test
@DisplayName("nameToPath creates a simple SQLite SQL name")
def nameToPathSQLiteSimpleSQL(): Unit =
assertEquals("data->>'Simple'", Field.nameToPath("Simple", Dialect.SQLITE, FieldFormat.SQL),
"Path not constructed correctly")
@Test
@DisplayName("nameToPath creates a nested PostgreSQL SQL name")
def nameToPathPostgresNestedSQL(): Unit =
assertEquals("data#>>'{A,Long,Path,to,the,Property}'",
Field.nameToPath("A.Long.Path.to.the.Property", Dialect.POSTGRESQL, FieldFormat.SQL),
"Path not constructed correctly")
@Test
@DisplayName("nameToPath creates a nested SQLite SQL name")
def nameToPathSQLiteNestedSQL(): Unit =
assertEquals("data->'A'->'Long'->'Path'->'to'->'the'->>'Property'",
Field.nameToPath("A.Long.Path.to.the.Property", Dialect.SQLITE, FieldFormat.SQL),
"Path not constructed correctly")
@Test
@DisplayName("nameToPath creates a simple PostgreSQL JSON name")
def nameToPathPostgresSimpleJSON(): Unit =
assertEquals("data->'Simple'", Field.nameToPath("Simple", Dialect.POSTGRESQL, FieldFormat.JSON),
"Path not constructed correctly")
@Test
@DisplayName("nameToPath creates a simple SQLite JSON name")
def nameToPathSQLiteSimpleJSON(): Unit =
assertEquals("data->'Simple'", Field.nameToPath("Simple", Dialect.SQLITE, FieldFormat.JSON),
"Path not constructed correctly")
@Test
@DisplayName("nameToPath creates a nested PostgreSQL JSON name")
def nameToPathPostgresNestedJSON(): Unit =
assertEquals("data#>'{A,Long,Path,to,the,Property}'",
Field.nameToPath("A.Long.Path.to.the.Property", Dialect.POSTGRESQL, FieldFormat.JSON),
"Path not constructed correctly")
@Test
@DisplayName("nameToPath creates a nested SQLite JSON name")
def nameToPathSQLiteNestedJSON(): Unit =
assertEquals("data->'A'->'Long'->'Path'->'to'->'the'->'Property'",
Field.nameToPath("A.Long.Path.to.the.Property", Dialect.SQLITE, FieldFormat.JSON),
"Path not constructed correctly")

View File

@@ -0,0 +1,79 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{AfterEach, DisplayName, Test}
import solutions.bitbadger.documents.{DocumentException, Field}
import solutions.bitbadger.documents.query.FindQuery
import scala.jdk.CollectionConverters.*
@DisplayName("Scala | Query | FindQuery")
class FindQueryTest:
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
def cleanUp(): Unit =
ForceDialect.none()
@Test
@DisplayName("all generates correctly")
def all(): Unit =
assertEquals(s"SELECT data FROM $TEST_TABLE", FindQuery.all(TEST_TABLE), "Find query not constructed correctly")
@Test
@DisplayName("byId generates correctly | PostgreSQL")
def byIdPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"SELECT data FROM $TEST_TABLE WHERE data->>'id' = :id", FindQuery.byId(TEST_TABLE),
"Find query not constructed correctly")
@Test
@DisplayName("byId generates correctly | SQLite")
def byIdSQLite(): Unit =
ForceDialect.sqlite()
assertEquals(s"SELECT data FROM $TEST_TABLE WHERE data->>'id' = :id", FindQuery.byId(TEST_TABLE),
"Find query not constructed correctly")
@Test
@DisplayName("byFields generates correctly | PostgreSQL")
def byFieldsPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"SELECT data FROM $TEST_TABLE WHERE data->>'a' = :b AND (data->>'c')::numeric < :d",
FindQuery.byFields(TEST_TABLE, List(Field.equal("a", "", ":b"), Field.less("c", 14, ":d")).asJava),
"Find query not constructed correctly")
@Test
@DisplayName("byFields generates correctly | SQLite")
def byFieldsSQLite(): Unit =
ForceDialect.sqlite()
assertEquals(s"SELECT data FROM $TEST_TABLE WHERE data->>'a' = :b AND data->>'c' < :d",
FindQuery.byFields(TEST_TABLE, List(Field.equal("a", "", ":b"), Field.less("c", 14, ":d")).asJava),
"Find query not constructed correctly")
@Test
@DisplayName("byContains generates correctly | PostgreSQL")
def byContainsPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"SELECT data FROM $TEST_TABLE WHERE data @> :criteria", FindQuery.byContains(TEST_TABLE),
"Find query not constructed correctly")
@Test
@DisplayName("byContains fails | SQLite")
def byContainsSQLite(): Unit =
ForceDialect.sqlite()
assertThrows(classOf[DocumentException], () => FindQuery.byContains(TEST_TABLE))
@Test
@DisplayName("byJsonPath generates correctly | PostgreSQL")
def byJsonPathPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"SELECT data FROM $TEST_TABLE WHERE jsonb_path_exists(data, :path::jsonpath)",
FindQuery.byJsonPath(TEST_TABLE), "Find query not constructed correctly")
@Test
@DisplayName("byJsonPath fails | SQLite")
def byJsonPathSQLite(): Unit =
ForceDialect.sqlite()
assertThrows(classOf[DocumentException], () => FindQuery.byJsonPath(TEST_TABLE))

View File

@@ -0,0 +1,17 @@
package solutions.bitbadger.documents.scala.tests
import solutions.bitbadger.documents.Configuration
/**
* These functions use a dummy connection string to force the given dialect for a given test
*/
object ForceDialect:
def postgres (): Unit =
Configuration.setConnectionString(":postgresql:")
def sqlite (): Unit =
Configuration.setConnectionString(":sqlite:")
def none (): Unit =
Configuration.setConnectionString(null)

View File

@@ -0,0 +1,3 @@
package solutions.bitbadger.documents.scala.tests
class IntIdClass(var id: Int)

View File

@@ -0,0 +1,3 @@
package solutions.bitbadger.documents.scala.tests
class LongIdClass(var id: Long)

View File

@@ -0,0 +1,63 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{DisplayName, Test}
import solutions.bitbadger.documents.Op
@DisplayName("Scala | Op")
class OpTest:
@Test
@DisplayName("EQUAL uses proper SQL")
def equalSQL(): Unit =
assertEquals("=", Op.EQUAL.getSql, "The SQL for equal is incorrect")
@Test
@DisplayName("GREATER uses proper SQL")
def greaterSQL(): Unit =
assertEquals(">", Op.GREATER.getSql, "The SQL for greater is incorrect")
@Test
@DisplayName("GREATER_OR_EQUAL uses proper SQL")
def greaterOrEqualSQL(): Unit =
assertEquals(">=", Op.GREATER_OR_EQUAL.getSql, "The SQL for greater-or-equal is incorrect")
@Test
@DisplayName("LESS uses proper SQL")
def lessSQL(): Unit =
assertEquals("<", Op.LESS.getSql, "The SQL for less is incorrect")
@Test
@DisplayName("LESS_OR_EQUAL uses proper SQL")
def lessOrEqualSQL(): Unit =
assertEquals("<=", Op.LESS_OR_EQUAL.getSql, "The SQL for less-or-equal is incorrect")
@Test
@DisplayName("NOT_EQUAL uses proper SQL")
def notEqualSQL(): Unit =
assertEquals("<>", Op.NOT_EQUAL.getSql, "The SQL for not-equal is incorrect")
@Test
@DisplayName("BETWEEN uses proper SQL")
def betweenSQL(): Unit =
assertEquals("BETWEEN", Op.BETWEEN.getSql, "The SQL for between is incorrect")
@Test
@DisplayName("IN uses proper SQL")
def inSQL(): Unit =
assertEquals("IN", Op.IN.getSql, "The SQL for in is incorrect")
@Test
@DisplayName("IN_ARRAY uses proper SQL")
def inArraySQL(): Unit =
assertEquals("??|", Op.IN_ARRAY.getSql, "The SQL for in-array is incorrect")
@Test
@DisplayName("EXISTS uses proper SQL")
def existsSQL(): Unit =
assertEquals("IS NOT NULL", Op.EXISTS.getSql, "The SQL for exists is incorrect")
@Test
@DisplayName("NOT_EXISTS uses proper SQL")
def notExistsSQL(): Unit =
assertEquals("IS NULL", Op.NOT_EXISTS.getSql, "The SQL for not-exists is incorrect")

View File

@@ -0,0 +1,24 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{DisplayName, Test}
import solutions.bitbadger.documents.ParameterName
@DisplayName("Scala | ParameterName")
class ParameterNameTest:
@Test
@DisplayName("derive works when given existing names")
def withExisting(): Unit =
val names = ParameterName()
assertEquals(":taco", names.derive(":taco"), "Name should have been :taco")
assertEquals(":field0", names.derive(null), "Counter should not have advanced for named field")
@Test
@DisplayName("derive works when given all anonymous fields")
def allAnonymous(): Unit =
val names = ParameterName()
assertEquals(":field0", names.derive(null), "Anonymous field name should have been returned")
assertEquals(":field1", names.derive(null), "Counter should have advanced from previous call")
assertEquals(":field2", names.derive(null), "Counter should have advanced from previous call")
assertEquals(":field3", names.derive(null), "Counter should have advanced from previous call")

View File

@@ -0,0 +1,29 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{DisplayName, Test}
import solutions.bitbadger.documents.{DocumentException, Parameter, ParameterType}
@DisplayName("Scala | Parameter")
class ParameterTest:
@Test
@DisplayName("Construction with colon-prefixed name")
def ctorWithColon(): Unit =
val p = Parameter(":test", ParameterType.STRING, "ABC")
assertEquals(":test", p.getName, "Parameter name was incorrect")
assertEquals(ParameterType.STRING, p.getType, "Parameter type was incorrect")
assertEquals("ABC", p.getValue, "Parameter value was incorrect")
@Test
@DisplayName("Construction with at-sign-prefixed name")
def ctorWithAtSign(): Unit =
val p = Parameter("@yo", ParameterType.NUMBER, null)
assertEquals("@yo", p.getName, "Parameter name was incorrect")
assertEquals(ParameterType.NUMBER, p.getType, "Parameter type was incorrect")
assertNull(p.getValue, "Parameter value was incorrect")
@Test
@DisplayName("Construction fails with incorrect prefix")
def ctorFailsForPrefix(): Unit =
assertThrows(classOf[DocumentException], () => Parameter("it", ParameterType.JSON, ""))

View File

@@ -0,0 +1,100 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{AfterEach, DisplayName, Test}
import solutions.bitbadger.documents.{DocumentException, Field, Parameter, ParameterType}
import solutions.bitbadger.documents.scala.Parameters
@DisplayName("Scala | Parameters")
class ParametersTest:
/**
* Reset the dialect
*/
@AfterEach
def cleanUp(): Unit =
ForceDialect.none()
@Test
@DisplayName("nameFields works with no changes")
def nameFieldsNoChange(): Unit =
val fields = Field.equal("a", "", ":test") :: Field.exists("q") :: Field.equal("b", "", ":me") :: Nil
val named = Parameters.nameFields(fields).toList
assertEquals(fields.size, named.size, "There should have been 3 fields in the list")
assertSame(fields.head, named.head, "The first field should be the same")
assertSame(fields(1), named(1), "The second field should be the same")
assertSame(fields(2), named(2), "The third field should be the same")
@Test
@DisplayName("nameFields works when changing fields")
def nameFieldsChange(): Unit =
val fields = Field.equal("a", "") :: Field.equal("e", "", ":hi") :: Field.equal("b", "") ::
Field.notExists("z") :: Nil
val named = Parameters.nameFields(fields).toList
assertEquals(fields.size, named.size, "There should have been 4 fields in the list")
assertNotSame(fields.head, named.head, "The first field should not be the same")
assertEquals(":field0", named.head.getParameterName, "First parameter name incorrect")
assertSame(fields(1), named(1), "The second field should be the same")
assertNotSame(fields(2), named(2), "The third field should not be the same")
assertEquals(":field1", named(2).getParameterName, "Third parameter name incorrect")
assertSame(fields(3), named(3), "The fourth field should be the same")
@Test
@DisplayName("replaceNamesInQuery replaces successfully")
def replaceNamesInQuery(): Unit =
val parameters = Parameter(":data", ParameterType.JSON, "{}") ::
Parameter(":data_ext", ParameterType.STRING, "") :: Nil
val query = "SELECT data, data_ext FROM tbl WHERE data = :data AND data_ext = :data_ext AND more_data = :data"
assertEquals("SELECT data, data_ext FROM tbl WHERE data = ? AND data_ext = ? AND more_data = ?",
Parameters.replaceNamesInQuery(query, parameters), "Parameters not replaced correctly")
@Test
@DisplayName("fieldNames generates a single parameter (PostgreSQL)")
def fieldNamesSinglePostgres(): Unit =
ForceDialect.postgres()
val nameParams = Parameters.fieldNames("test" :: Nil).toList
assertEquals(1, nameParams.size, "There should be one name parameter")
assertEquals(":name", nameParams.head.getName, "The parameter name is incorrect")
assertEquals(ParameterType.STRING, nameParams.head.getType, "The parameter type is incorrect")
assertEquals("{test}", nameParams.head.getValue, "The parameter value is incorrect")
@Test
@DisplayName("fieldNames generates multiple parameters (PostgreSQL)")
def fieldNamesMultiplePostgres(): Unit =
ForceDialect.postgres()
val nameParams = Parameters.fieldNames("test" :: "this" :: "today" :: Nil).toList
assertEquals(1, nameParams.size, "There should be one name parameter")
assertEquals(":name", nameParams.head.getName, "The parameter name is incorrect")
assertEquals(ParameterType.STRING, nameParams.head.getType, "The parameter type is incorrect")
assertEquals("{test,this,today}", nameParams.head.getValue, "The parameter value is incorrect")
@Test
@DisplayName("fieldNames generates a single parameter (SQLite)")
def fieldNamesSingleSQLite(): Unit =
ForceDialect.sqlite()
val nameParams = Parameters.fieldNames("test" :: Nil).toList
assertEquals(1, nameParams.size, "There should be one name parameter")
assertEquals(":name0", nameParams.head.getName, "The parameter name is incorrect")
assertEquals(ParameterType.STRING, nameParams.head.getType, "The parameter type is incorrect")
assertEquals("test", nameParams.head.getValue, "The parameter value is incorrect")
@Test
@DisplayName("fieldNames generates multiple parameters (SQLite)")
def fieldNamesMultipleSQLite(): Unit =
ForceDialect.sqlite()
val nameParams = Parameters.fieldNames("test" :: "this" :: "today" :: Nil).toList
assertEquals(3, nameParams.size, "There should be one name parameter")
assertEquals(":name0", nameParams.head.getName, "The first parameter name is incorrect")
assertEquals(ParameterType.STRING, nameParams.head.getType, "The first parameter type is incorrect")
assertEquals("test", nameParams.head.getValue, "The first parameter value is incorrect")
assertEquals(":name1", nameParams(1).getName, "The second parameter name is incorrect")
assertEquals(ParameterType.STRING, nameParams(1).getType, "The second parameter type is incorrect")
assertEquals("this", nameParams(1).getValue, "The second parameter value is incorrect")
assertEquals(":name2", nameParams(2).getName, "The third parameter name is incorrect")
assertEquals(ParameterType.STRING, nameParams(2).getType, "The third parameter type is incorrect")
assertEquals("today", nameParams(2).getValue, "The third parameter value is incorrect")
@Test
@DisplayName("fieldNames fails if dialect not set")
def fieldNamesFails(): Unit =
assertThrows(classOf[DocumentException], () => Parameters.fieldNames(List()))

View File

@@ -0,0 +1,72 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{AfterEach, DisplayName, Test}
import solutions.bitbadger.documents.{DocumentException, Field}
import solutions.bitbadger.documents.query.PatchQuery
import scala.jdk.CollectionConverters.*
@DisplayName("Scala | Query | PatchQuery")
class PatchQueryTest:
/**
* Reset the dialect
*/
@AfterEach
def cleanUp(): Unit =
ForceDialect.none()
@Test
@DisplayName("byId generates correctly | PostgreSQL")
def byIdPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"UPDATE $TEST_TABLE SET data = data || :data WHERE data->>'id' = :id", PatchQuery.byId(TEST_TABLE),
"Patch query not constructed correctly")
@Test
@DisplayName("byId generates correctly | SQLite")
def byIdSQLite(): Unit =
ForceDialect.sqlite()
assertEquals(s"UPDATE $TEST_TABLE SET data = json_patch(data, json(:data)) WHERE data->>'id' = :id",
PatchQuery.byId(TEST_TABLE), "Patch query not constructed correctly")
@Test
@DisplayName("byFields generates correctly | PostgreSQL")
def byFieldsPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"UPDATE $TEST_TABLE SET data = data || :data WHERE data->>'z' = :y",
PatchQuery.byFields(TEST_TABLE, List(Field.equal("z", "", ":y")).asJava), "Patch query not constructed correctly")
@Test
@DisplayName("byFields generates correctly | SQLite")
def byFieldsSQLite(): Unit =
ForceDialect.sqlite()
assertEquals(s"UPDATE $TEST_TABLE SET data = json_patch(data, json(:data)) WHERE data->>'z' = :y",
PatchQuery.byFields(TEST_TABLE, List(Field.equal("z", "", ":y")).asJava), "Patch query not constructed correctly")
@Test
@DisplayName("byContains generates correctly | PostgreSQL")
def byContainsPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"UPDATE $TEST_TABLE SET data = data || :data WHERE data @> :criteria",
PatchQuery.byContains(TEST_TABLE), "Patch query not constructed correctly" )
@Test
@DisplayName("byContains fails | SQLite")
def byContainsSQLite(): Unit =
ForceDialect.sqlite()
assertThrows(classOf[DocumentException], () => PatchQuery.byContains(TEST_TABLE))
@Test
@DisplayName("byJsonPath generates correctly | PostgreSQL")
def byJsonPathPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"UPDATE $TEST_TABLE SET data = data || :data WHERE jsonb_path_exists(data, :path::jsonpath)",
PatchQuery.byJsonPath(TEST_TABLE), "Patch query not constructed correctly")
@Test
@DisplayName("byJsonPath fails | SQLite")
def byJsonPathSQLite(): Unit =
ForceDialect.sqlite()
assertThrows(classOf[DocumentException], () => PatchQuery.byJsonPath(TEST_TABLE))

View File

@@ -0,0 +1,136 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{AfterEach, DisplayName, Test}
import solutions.bitbadger.documents.{Dialect, Field, FieldMatch}
import solutions.bitbadger.documents.query.QueryUtils
import scala.jdk.CollectionConverters.*
@DisplayName("Scala | Query | Package Functions")
class QueryUtilsTest:
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
def cleanUp(): Unit =
ForceDialect.none()
@Test
@DisplayName("statementWhere generates correctly")
def statementWhere(): Unit =
assertEquals("x WHERE y", QueryUtils.statementWhere("x", "y"), "Statements not combined correctly")
@Test
@DisplayName("byId generates a numeric ID query | PostgreSQL")
def byIdNumericPostgres(): Unit =
ForceDialect.postgres()
assertEquals("test WHERE (data->>'id')::numeric = :id", QueryUtils.byId("test", 9))
@Test
@DisplayName("byId generates an alphanumeric ID query | PostgreSQL")
def byIdAlphaPostgres(): Unit =
ForceDialect.postgres()
assertEquals("unit WHERE data->>'id' = :id", QueryUtils.byId("unit", "18"))
@Test
@DisplayName("byId generates ID query | SQLite")
def byIdSQLite(): Unit =
ForceDialect.sqlite()
assertEquals("yo WHERE data->>'id' = :id", QueryUtils.byId("yo", 27))
@Test
@DisplayName("byFields generates default field query | PostgreSQL")
def byFieldsMultipleDefaultPostgres(): Unit =
ForceDialect.postgres()
assertEquals("this WHERE data->>'a' = :the_a AND (data->>'b')::numeric = :b_value",
QueryUtils.byFields("this", List(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")).asJava))
@Test
@DisplayName("byFields generates default field query | SQLite")
def byFieldsMultipleDefaultSQLite(): Unit =
ForceDialect.sqlite()
assertEquals("this WHERE data->>'a' = :the_a AND data->>'b' = :b_value",
QueryUtils.byFields("this", List(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")).asJava))
@Test
@DisplayName("byFields generates ANY field query | PostgreSQL")
def byFieldsMultipleAnyPostgres(): Unit =
ForceDialect.postgres()
assertEquals("that WHERE data->>'a' = :the_a OR (data->>'b')::numeric = :b_value",
QueryUtils.byFields("that", List(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")).asJava,
FieldMatch.ANY))
@Test
@DisplayName("byFields generates ANY field query | SQLite")
def byFieldsMultipleAnySQLite(): Unit =
ForceDialect.sqlite()
assertEquals("that WHERE data->>'a' = :the_a OR data->>'b' = :b_value",
QueryUtils.byFields("that", List(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")).asJava,
FieldMatch.ANY))
@Test
@DisplayName("orderBy generates for no fields")
def orderByNone(): Unit =
assertEquals("", QueryUtils.orderBy(List().asJava, Dialect.POSTGRESQL),
"ORDER BY should have been blank (PostgreSQL)")
assertEquals("", QueryUtils.orderBy(List().asJava, Dialect.SQLITE), "ORDER BY should have been blank (SQLite)")
@Test
@DisplayName("orderBy generates single, no direction | PostgreSQL")
def orderBySinglePostgres(): Unit =
assertEquals(" ORDER BY data->>'TestField'",
QueryUtils.orderBy(List(Field.named("TestField")).asJava, Dialect.POSTGRESQL),
"ORDER BY not constructed correctly")
@Test
@DisplayName("orderBy generates single, no direction | SQLite")
def orderBySingleSQLite(): Unit =
assertEquals(" ORDER BY data->>'TestField'",
QueryUtils.orderBy(List(Field.named("TestField")).asJava, Dialect.SQLITE),
"ORDER BY not constructed correctly")
@Test
@DisplayName("orderBy generates multiple with direction | PostgreSQL")
def orderByMultiplePostgres(): Unit =
assertEquals(" ORDER BY data#>>'{Nested,Test,Field}' DESC, data->>'AnotherField', data->>'It' DESC",
QueryUtils.orderBy(
List(Field.named("Nested.Test.Field DESC"), Field.named("AnotherField"), Field.named("It DESC")).asJava,
Dialect.POSTGRESQL),
"ORDER BY not constructed correctly")
@Test
@DisplayName("orderBy generates multiple with direction | SQLite")
def orderByMultipleSQLite(): Unit =
assertEquals(" ORDER BY data->'Nested'->'Test'->>'Field' DESC, data->>'AnotherField', data->>'It' DESC",
QueryUtils.orderBy(
List(Field.named("Nested.Test.Field DESC"), Field.named("AnotherField"), Field.named("It DESC")).asJava,
Dialect.SQLITE),
"ORDER BY not constructed correctly")
@Test
@DisplayName("orderBy generates numeric ordering | PostgreSQL")
def orderByNumericPostgres(): Unit =
assertEquals(" ORDER BY (data->>'Test')::numeric",
QueryUtils.orderBy(List(Field.named("n:Test")).asJava, Dialect.POSTGRESQL), "ORDER BY not constructed correctly")
@Test
@DisplayName("orderBy generates numeric ordering | SQLite")
def orderByNumericSQLite(): Unit =
assertEquals(" ORDER BY data->>'Test'", QueryUtils.orderBy(List(Field.named("n:Test")).asJava, Dialect.SQLITE),
"ORDER BY not constructed correctly")
@Test
@DisplayName("orderBy generates case-insensitive ordering | PostgreSQL")
def orderByCIPostgres(): Unit =
assertEquals(" ORDER BY LOWER(data#>>'{Test,Field}') DESC NULLS FIRST",
QueryUtils.orderBy(List(Field.named("i:Test.Field DESC NULLS FIRST")).asJava, Dialect.POSTGRESQL),
"ORDER BY not constructed correctly")
@Test
@DisplayName("orderBy generates case-insensitive ordering | SQLite")
def orderByCISQLite(): Unit =
assertEquals(" ORDER BY data->'Test'->>'Field' COLLATE NOCASE ASC NULLS LAST",
QueryUtils.orderBy(List(Field.named("i:Test.Field ASC NULLS LAST")).asJava, Dialect.SQLITE),
"ORDER BY not constructed correctly")

View File

@@ -0,0 +1,82 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{AfterEach, DisplayName, Test}
import solutions.bitbadger.documents.{DocumentException, Field, Parameter, ParameterType}
import solutions.bitbadger.documents.query.RemoveFieldsQuery
import scala.jdk.CollectionConverters.*
@DisplayName("Scala | Query | RemoveFieldsQuery")
class RemoveFieldsQueryTest:
/**
* Reset the dialect
*/
@AfterEach
def cleanUp(): Unit =
ForceDialect.none()
@Test
@DisplayName("byId generates correctly | PostgreSQL")
def byIdPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"UPDATE $TEST_TABLE SET data = data - :name::text[] WHERE data->>'id' = :id",
RemoveFieldsQuery.byId(TEST_TABLE, List(Parameter(":name", ParameterType.STRING, "{a,z}")).asJava),
"Remove Fields query not constructed correctly")
@Test
@DisplayName("byId generates correctly | SQLite")
def byIdSQLite(): Unit =
ForceDialect.sqlite()
assertEquals(s"UPDATE $TEST_TABLE SET data = json_remove(data, :name0, :name1) WHERE data->>'id' = :id",
RemoveFieldsQuery.byId(TEST_TABLE,
List(Parameter(":name0", ParameterType.STRING, "a"),Parameter(":name1", ParameterType.STRING, "z")).asJava),
"Remove Field query not constructed correctly")
@Test
@DisplayName("byFields generates correctly | PostgreSQL")
def byFieldsPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"UPDATE $TEST_TABLE SET data = data - :name::text[] WHERE data->>'f' > :g",
RemoveFieldsQuery.byFields(TEST_TABLE, List(Parameter(":name", ParameterType.STRING, "{b,c}")).asJava,
List(Field.greater("f", "", ":g")).asJava),
"Remove Field query not constructed correctly")
@Test
@DisplayName("byFields generates correctly | SQLite")
def byFieldsSQLite(): Unit =
ForceDialect.sqlite()
assertEquals(s"UPDATE $TEST_TABLE SET data = json_remove(data, :name0, :name1) WHERE data->>'f' > :g",
RemoveFieldsQuery.byFields(TEST_TABLE,
List(Parameter(":name0", ParameterType.STRING, "b"), Parameter(":name1", ParameterType.STRING, "c")).asJava,
List(Field.greater("f", "", ":g")).asJava),
"Remove Field query not constructed correctly")
@Test
@DisplayName("byContains generates correctly | PostgreSQL")
def byContainsPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"UPDATE $TEST_TABLE SET data = data - :name::text[] WHERE data @> :criteria",
RemoveFieldsQuery.byContains(TEST_TABLE, List(Parameter(":name", ParameterType.STRING, "{m,n}")).asJava),
"Remove Field query not constructed correctly")
@Test
@DisplayName("byContains fails | SQLite")
def byContainsSQLite(): Unit =
ForceDialect.sqlite()
assertThrows(classOf[DocumentException], () => RemoveFieldsQuery.byContains(TEST_TABLE, List().asJava))
@Test
@DisplayName("byJsonPath generates correctly | PostgreSQL")
def byJsonPathPostgres(): Unit =
ForceDialect.postgres()
assertEquals(s"UPDATE $TEST_TABLE SET data = data - :name::text[] WHERE jsonb_path_exists(data, :path::jsonpath)",
RemoveFieldsQuery.byJsonPath(TEST_TABLE, List(Parameter(":name", ParameterType.STRING, "{o,p}")).asJava),
"Remove Field query not constructed correctly")
@Test
@DisplayName("byJsonPath fails | SQLite")
def byJsonPathSQLite(): Unit =
ForceDialect.sqlite()
assertThrows(classOf[DocumentException], () => RemoveFieldsQuery.byJsonPath(TEST_TABLE, List().asJava))

View File

@@ -0,0 +1,3 @@
package solutions.bitbadger.documents.scala.tests
class ShortIdClass(var id: Short)

View File

@@ -0,0 +1,3 @@
package solutions.bitbadger.documents.scala.tests
class StringIdClass(var id: String)

View File

@@ -0,0 +1,141 @@
package solutions.bitbadger.documents.scala.tests
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.{AfterEach, DisplayName, Test}
import solutions.bitbadger.documents.{DocumentException, Field, FieldMatch}
import solutions.bitbadger.documents.query.Where
import scala.jdk.CollectionConverters.*
@DisplayName("Scala | Query | Where")
class WhereTest:
/**
* Clear the connection string (resets Dialect)
*/
@AfterEach
def cleanUp (): Unit =
ForceDialect.none()
@Test
@DisplayName("byFields is blank when given no fields")
def byFieldsBlankIfEmpty(): Unit =
assertEquals("", Where.byFields(List().asJava))
@Test
@DisplayName("byFields generates one numeric field | PostgreSQL")
def byFieldsOneFieldPostgres(): Unit =
ForceDialect.postgres()
assertEquals("(data->>'it')::numeric = :that", Where.byFields(List(Field.equal("it", 9, ":that")).asJava))
@Test
@DisplayName("byFields generates one alphanumeric field | PostgreSQL")
def byFieldsOneAlphaFieldPostgres(): Unit =
ForceDialect.postgres()
assertEquals("data->>'it' = :that", Where.byFields(List(Field.equal("it", "", ":that")).asJava))
@Test
@DisplayName("byFields generates one field | SQLite")
def byFieldsOneFieldSQLite(): Unit =
ForceDialect.sqlite()
assertEquals("data->>'it' = :that", Where.byFields(List(Field.equal("it", "", ":that")).asJava))
@Test
@DisplayName("byFields generates multiple fields w/ default match | PostgreSQL")
def byFieldsMultipleDefaultPostgres(): Unit =
ForceDialect.postgres()
assertEquals("data->>'1' = :one AND (data->>'2')::numeric = :two AND data->>'3' = :three",
Where.byFields(
List(Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three")).asJava))
@Test
@DisplayName("byFields generates multiple fields w/ default match | SQLite")
def byFieldsMultipleDefaultSQLite(): Unit =
ForceDialect.sqlite()
assertEquals("data->>'1' = :one AND data->>'2' = :two AND data->>'3' = :three",
Where.byFields(
List(Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three")).asJava))
@Test
@DisplayName("byFields generates multiple fields w/ ANY match | PostgreSQL")
def byFieldsMultipleAnyPostgres(): Unit =
ForceDialect.postgres()
assertEquals("data->>'1' = :one OR (data->>'2')::numeric = :two OR data->>'3' = :three",
Where.byFields(
List(Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three")).asJava,
FieldMatch.ANY))
@Test
@DisplayName("byFields generates multiple fields w/ ANY match | SQLite")
def byFieldsMultipleAnySQLite(): Unit =
ForceDialect.sqlite()
assertEquals("data->>'1' = :one OR data->>'2' = :two OR data->>'3' = :three",
Where.byFields(
List(Field.equal("1", "", ":one"), Field.equal("2", 0L, ":two"), Field.equal("3", "", ":three")).asJava,
FieldMatch.ANY))
@Test
@DisplayName("byId generates defaults for alphanumeric key | PostgreSQL")
def byIdDefaultAlphaPostgres(): Unit =
ForceDialect.postgres()
assertEquals("data->>'id' = :id", Where.byId())
@Test
@DisplayName("byId generates defaults for numeric key | PostgreSQL")
def byIdDefaultNumericPostgres(): Unit =
ForceDialect.postgres()
assertEquals("(data->>'id')::numeric = :id", Where.byId(":id", 5))
@Test
@DisplayName("byId generates defaults | SQLite")
def byIdDefaultSQLite(): Unit =
ForceDialect.sqlite()
assertEquals("data->>'id' = :id", Where.byId())
@Test
@DisplayName("byId generates named ID | PostgreSQL")
def byIdDefaultNamedPostgres(): Unit =
ForceDialect.postgres()
assertEquals("data->>'id' = :key", Where.byId(":key"))
@Test
@DisplayName("byId generates named ID | SQLite")
def byIdDefaultNamedSQLite(): Unit =
ForceDialect.sqlite()
assertEquals("data->>'id' = :key", Where.byId(":key"))
@Test
@DisplayName("jsonContains generates defaults | PostgreSQL")
def jsonContainsDefaultPostgres(): Unit =
ForceDialect.postgres()
assertEquals("data @> :criteria", Where.jsonContains())
@Test
@DisplayName("jsonContains generates named parameter | PostgreSQL")
def jsonContainsNamedPostgres(): Unit =
ForceDialect.postgres()
assertEquals("data @> :it", Where.jsonContains(":it"))
@Test
@DisplayName("jsonContains fails | SQLite")
def jsonContainsFailsSQLite(): Unit =
ForceDialect.sqlite()
assertThrows(classOf[DocumentException], () => Where.jsonContains())
@Test
@DisplayName("jsonPathMatches generates defaults | PostgreSQL")
def jsonPathMatchDefaultPostgres(): Unit =
ForceDialect.postgres()
assertEquals("jsonb_path_exists(data, :path::jsonpath)", Where.jsonPathMatches())
@Test
@DisplayName("jsonPathMatches generates named parameter | PostgreSQL")
def jsonPathMatchNamedPostgres(): Unit =
ForceDialect.postgres()
assertEquals("jsonb_path_exists(data, :jp::jsonpath)", Where.jsonPathMatches(":jp"))
@Test
@DisplayName("jsonPathMatches fails | SQLite")
def jsonPathFailsSQLite(): Unit =
ForceDialect.sqlite()
assertThrows(classOf[DocumentException], () => Where.jsonPathMatches())

View File

@@ -0,0 +1,11 @@
package solutions.bitbadger.documents.scala.tests.integration
class ArrayDocument(val id: String = "", val values: List[String] = List())
object ArrayDocument:
/** A set of documents used for integration tests */
val testDocuments: List[ArrayDocument] =
ArrayDocument("first", "a" :: "b" :: "c" :: Nil) ::
ArrayDocument("second", "c" :: "d" :: "e" :: Nil) ::
ArrayDocument("third", "x" :: "y" :: "z" :: Nil) :: Nil

View File

@@ -0,0 +1,42 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.Assertions.*
import solutions.bitbadger.documents.Field
import solutions.bitbadger.documents.scala.extensions.*
import solutions.bitbadger.documents.scala.tests.TEST_TABLE
object CountFunctions:
def all(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(5L, db.conn.countAll(TEST_TABLE), "There should have been 5 documents in the table")
def byFieldsNumeric(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(3L, db.conn.countByFields(TEST_TABLE, Field.between("numValue", 10, 20) :: Nil),
"There should have been 3 matching documents")
def byFieldsAlpha(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(1L, db.conn.countByFields(TEST_TABLE, Field.between("value", "aardvark", "apple") :: Nil),
"There should have been 1 matching document")
def byContainsMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(2L, db.conn.countByContains(TEST_TABLE, Map.Map1("value", "purple")),
"There should have been 2 matching documents")
def byContainsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(0L, db.conn.countByContains(TEST_TABLE, Map.Map1("value", "magenta")),
"There should have been no matching documents")
def byJsonPathMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(2L, db.conn.countByJsonPath(TEST_TABLE, "$.numValue ? (@ < 5)"),
"There should have been 2 matching documents")
def byJsonPathNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(0L, db.conn.countByJsonPath(TEST_TABLE, "$.numValue ? (@ > 100)"),
"There should have been no matching documents")

View File

@@ -0,0 +1,52 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.Assertions.*
import solutions.bitbadger.documents.query.{CountQuery, DeleteQuery, FindQuery}
import solutions.bitbadger.documents.scala.Results
import solutions.bitbadger.documents.scala.extensions.*
import solutions.bitbadger.documents.scala.tests.TEST_TABLE
import solutions.bitbadger.documents.{Configuration, Field, Parameter, ParameterType}
object CustomFunctions:
def listEmpty(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
db.conn.deleteByFields(TEST_TABLE, Field.exists(Configuration.idField) :: Nil)
val result = db.conn.customList[JsonDocument](FindQuery.all(TEST_TABLE), Results.fromData)
assertEquals(0, result.size, "There should have been no results")
def listAll(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val result = db.conn.customList[JsonDocument](FindQuery.all(TEST_TABLE), Results.fromData)
assertEquals(5, result.size, "There should have been 5 results")
def singleNone(db: ThrowawayDatabase): Unit =
assertTrue(db.conn.customSingle[JsonDocument](FindQuery.all(TEST_TABLE), Results.fromData).isEmpty,
"There should not have been a document returned")
def singleOne(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertTrue(db.conn.customSingle[JsonDocument](FindQuery.all(TEST_TABLE), Results.fromData).isDefined,
"There should have been a document returned")
def nonQueryChanges(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(5L, db.conn.customScalar[Long](CountQuery.all(TEST_TABLE), Results.toCount),
"There should have been 5 documents in the table")
db.conn.customNonQuery(s"DELETE FROM $TEST_TABLE")
assertEquals(0L, db.conn.customScalar[Long](CountQuery.all(TEST_TABLE), Results.toCount),
"There should have been no documents in the table")
def nonQueryNoChanges(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(5L, db.conn.customScalar[Long](CountQuery.all(TEST_TABLE), Results.toCount),
"There should have been 5 documents in the table")
db.conn.customNonQuery(DeleteQuery.byId(TEST_TABLE, "eighty-two"),
Parameter(":id", ParameterType.STRING, "eighty-two") :: Nil)
assertEquals(5L, db.conn.customScalar[Long](CountQuery.all(TEST_TABLE), Results.toCount),
"There should still have been 5 documents in the table")
def scalar(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(3L, db.conn.customScalar[Long](s"SELECT 3 AS it FROM $TEST_TABLE LIMIT 1", Results.toCount),
"The number 3 should have been returned")

View File

@@ -0,0 +1,36 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.Assertions.*
import solutions.bitbadger.documents.DocumentIndex
import solutions.bitbadger.documents.scala.extensions.*
import solutions.bitbadger.documents.scala.tests.TEST_TABLE
object DefinitionFunctions:
def ensureTable(db: ThrowawayDatabase): Unit =
assertFalse(db.dbObjectExists("ensured"), "The 'ensured' table should not exist")
assertFalse(db.dbObjectExists("idx_ensured_key"), "The PK index for the 'ensured' table should not exist")
db.conn.ensureTable("ensured")
assertTrue(db.dbObjectExists("ensured"), "The 'ensured' table should exist")
assertTrue(db.dbObjectExists("idx_ensured_key"), "The PK index for the 'ensured' table should now exist")
def ensureFieldIndex(db: ThrowawayDatabase): Unit =
assertFalse(db.dbObjectExists("idx_${TEST_TABLE}_test"), "The test index should not exist")
db.conn.ensureFieldIndex(TEST_TABLE, "test", "id" :: "category" :: Nil)
assertTrue(db.dbObjectExists(s"idx_${TEST_TABLE}_test"), "The test index should now exist")
def ensureDocumentIndexFull(db: ThrowawayDatabase): Unit =
assertFalse(db.dbObjectExists("doc_table"), "The 'doc_table' table should not exist")
db.conn.ensureTable("doc_table")
assertTrue(db.dbObjectExists("doc_table"), "The 'doc_table' table should exist")
assertFalse(db.dbObjectExists("idx_doc_table_document"), "The document index should not exist")
db.conn.ensureDocumentIndex("doc_table", DocumentIndex.FULL)
assertTrue(db.dbObjectExists("idx_doc_table_document"), "The document index should exist")
def ensureDocumentIndexOptimized(db: ThrowawayDatabase): Unit =
assertFalse(db.dbObjectExists("doc_table"), "The 'doc_table' table should not exist")
db.conn.ensureTable("doc_table")
assertTrue(db.dbObjectExists("doc_table"), "The 'doc_table' table should exist")
assertFalse(db.dbObjectExists("idx_doc_table_document"), "The document index should not exist")
db.conn.ensureDocumentIndex("doc_table", DocumentIndex.OPTIMIZED)
assertTrue(db.dbObjectExists("idx_doc_table_document"), "The document index should exist")

View File

@@ -0,0 +1,56 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.Assertions.*
import solutions.bitbadger.documents.Field
import solutions.bitbadger.documents.scala.extensions.*
import solutions.bitbadger.documents.scala.tests.TEST_TABLE
object DeleteFunctions:
def byIdMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(5L, db.conn.countAll(TEST_TABLE), "There should be 5 documents in the table")
db.conn.deleteById(TEST_TABLE, "four")
assertEquals(4L, db.conn.countAll(TEST_TABLE), "There should now be 4 documents in the table")
def byIdNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(5L, db.conn.countAll(TEST_TABLE), "There should be 5 documents in the table")
db.conn.deleteById(TEST_TABLE, "negative four")
assertEquals(5L, db.conn.countAll(TEST_TABLE), "There should still be 5 documents in the table")
def byFieldsMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(5L, db.conn.countAll(TEST_TABLE), "There should be 5 documents in the table")
db.conn.deleteByFields(TEST_TABLE, Field.notEqual("value", "purple") :: Nil)
assertEquals(2L, db.conn.countAll(TEST_TABLE), "There should now be 2 documents in the table")
def byFieldsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(5L, db.conn.countAll(TEST_TABLE), "There should be 5 documents in the table")
db.conn.deleteByFields(TEST_TABLE, Field.equal("value", "crimson") :: Nil)
assertEquals(5L, db.conn.countAll(TEST_TABLE), "There should still be 5 documents in the table")
def byContainsMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(5L, db.conn.countAll(TEST_TABLE), "There should be 5 documents in the table")
db.conn.deleteByContains(TEST_TABLE, Map.Map1("value", "purple"))
assertEquals(3L, db.conn.countAll(TEST_TABLE), "There should now be 3 documents in the table")
def byContainsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(5L, db.conn.countAll(TEST_TABLE), "There should be 5 documents in the table")
db.conn.deleteByContains(TEST_TABLE, Map.Map1("target", "acquired"))
assertEquals(5L, db.conn.countAll(TEST_TABLE), "There should still be 5 documents in the table")
def byJsonPathMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(5L, db.conn.countAll(TEST_TABLE), "There should be 5 documents in the table")
db.conn.deleteByJsonPath(TEST_TABLE, "$.value ? (@ == \"purple\")")
assertEquals(3L, db.conn.countAll(TEST_TABLE), "There should now be 3 documents in the table")
def byJsonPathNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(5L, db.conn.countAll(TEST_TABLE), "There should be 5 documents in the table")
db.conn.deleteByJsonPath(TEST_TABLE, "$.numValue ? (@ > 100)")
assertEquals(5L, db.conn.countAll(TEST_TABLE), "There should still be 5 documents in the table")

View File

@@ -0,0 +1,111 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.Assertions.*
import solutions.bitbadger.documents.{AutoId, Configuration, DocumentException, Field}
import solutions.bitbadger.documents.scala.extensions.*
import solutions.bitbadger.documents.scala.tests.TEST_TABLE
object DocumentFunctions:
import org.junit.jupiter.api.Assertions.assertThrows
def insertDefault(db: ThrowawayDatabase): Unit =
assertEquals(0L, db.conn.countAll(TEST_TABLE), "There should be no documents in the table")
val doc = JsonDocument("turkey", "", 0, SubDocument("gobble", "gobble"))
db.conn.insert(TEST_TABLE, doc)
val after = db.conn.findAll[JsonDocument](TEST_TABLE)
assertEquals(1, after.size, "There should be one document in the table")
assertEquals(doc, after.head, "The document should be what was inserted")
def insertDupe(db: ThrowawayDatabase): Unit =
db.conn.insert(TEST_TABLE, JsonDocument("a", "", 0, null))
assertThrows(classOf[DocumentException], () => db.conn.insert(TEST_TABLE, JsonDocument("a", "b", 22, null)),
"Inserting a document with a duplicate key should have thrown an exception")
def insertNumAutoId(db: ThrowawayDatabase): Unit =
try {
Configuration.autoIdStrategy = AutoId.NUMBER
Configuration.idField = "key"
assertEquals(0L, db.conn.countAll(TEST_TABLE), "There should be no documents in the table")
db.conn.insert(TEST_TABLE, NumIdDocument(0, "one"))
db.conn.insert(TEST_TABLE, NumIdDocument(0, "two"))
db.conn.insert(TEST_TABLE, NumIdDocument(77, "three"))
db.conn.insert(TEST_TABLE, NumIdDocument(0, "four"))
val after = db.conn.findAll[NumIdDocument](TEST_TABLE, Field.named("key") :: Nil)
assertEquals(4, after.size, "There should have been 4 documents returned")
assertEquals("1|2|77|78", after.fold("") { (acc, item) => s"$acc|$item" }, "The IDs were not generated correctly")
} finally {
Configuration.autoIdStrategy = AutoId.DISABLED
Configuration.idField = "id"
}
def insertUUIDAutoId(db: ThrowawayDatabase): Unit =
try {
Configuration.autoIdStrategy = AutoId.UUID
assertEquals(0L, db.conn.countAll(TEST_TABLE), "There should be no documents in the table")
db.conn.insert(TEST_TABLE, JsonDocument(""))
val after = db.conn.findAll[JsonDocument](TEST_TABLE)
assertEquals(1, after.size, "There should have been 1 document returned")
assertEquals(32, after.head.id.length, "The ID was not generated correctly")
} finally {
Configuration.autoIdStrategy = AutoId.DISABLED
}
def insertStringAutoId(db: ThrowawayDatabase): Unit =
try {
Configuration.autoIdStrategy = AutoId.RANDOM_STRING
assertEquals(0L, db.conn.countAll(TEST_TABLE), "There should be no documents in the table")
db.conn.insert(TEST_TABLE, JsonDocument(""))
Configuration.idStringLength = 21
db.conn.insert(TEST_TABLE, JsonDocument(""))
val after = db.conn.findAll[JsonDocument](TEST_TABLE)
assertEquals(2, after.size, "There should have been 2 documents returned")
assertEquals(16, after.head.id.length, "The first document's ID was not generated correctly")
assertEquals(21, after(1).id.length, "The second document's ID was not generated correctly")
} finally {
Configuration.autoIdStrategy = AutoId.DISABLED
Configuration.idStringLength = 16
}
def saveMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
db.conn.save(TEST_TABLE, JsonDocument("two", numValue = 44))
val tryDoc = db.conn.findById[String, JsonDocument](TEST_TABLE, "two")
assertTrue(tryDoc.isDefined, "There should have been a document returned")
val doc = tryDoc.get
assertEquals("two", doc.id, "An incorrect document was returned")
assertEquals("", doc.value, "The \"value\" field was not updated")
assertEquals(44, doc.numValue, "The \"numValue\" field was not updated")
assertNull(doc.sub, "The \"sub\" field was not updated")
def saveNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
db.conn.save(TEST_TABLE, JsonDocument("test", sub = SubDocument("a", "b")))
assertTrue(db.conn.findById[String, JsonDocument](TEST_TABLE, "test").isDefined,
"The test document should have been saved")
def updateMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
db.conn.update(TEST_TABLE, "one", JsonDocument("one", "howdy", 8, SubDocument("y", "z")))
val tryDoc = db.conn.findById[String, JsonDocument](TEST_TABLE, "one")
assertTrue(tryDoc.isDefined, "There should have been a document returned")
val doc = tryDoc.get
assertEquals("one", doc.id, "An incorrect document was returned")
assertEquals("howdy", doc.value, "The \"value\" field was not updated")
assertEquals(8, doc.numValue, "The \"numValue\" field was not updated")
assertNotNull(doc.sub, "The sub-document should not be null")
assertEquals("y", doc.sub.foo, "The sub-document \"foo\" field was not updated")
assertEquals("z", doc.sub.bar, "The sub-document \"bar\" field was not updated")
def updateNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertFalse(db.conn.existsById(TEST_TABLE, "two-hundred"))
db.conn.update(TEST_TABLE, "two-hundred", JsonDocument("two-hundred", numValue = 200))
assertFalse(db.conn.existsById(TEST_TABLE, "two-hundred"))

View File

@@ -0,0 +1,46 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.Assertions.*
import solutions.bitbadger.documents.Field
import solutions.bitbadger.documents.scala.extensions.*
import solutions.bitbadger.documents.scala.tests.TEST_TABLE
object ExistsFunctions:
def byIdMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertTrue(db.conn.existsById(TEST_TABLE, "three"), "The document with ID \"three\" should exist")
def byIdNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertFalse(db.conn.existsById(TEST_TABLE, "seven"), "The document with ID \"seven\" should not exist")
def byFieldsMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertTrue(db.conn.existsByFields(TEST_TABLE, Field.equal("numValue", 10) :: Nil),
"Matching documents should have been found")
def byFieldsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertFalse(db.conn.existsByFields(TEST_TABLE, Field.equal("nothing", "none") :: Nil),
"No matching documents should have been found")
def byContainsMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertTrue(db.conn.existsByContains(TEST_TABLE, Map.Map1("value", "purple")),
"Matching documents should have been found")
def byContainsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertFalse(db.conn.existsByContains(TEST_TABLE, Map.Map1("value", "violet")),
"Matching documents should not have been found")
def byJsonPathMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertTrue(db.conn.existsByJsonPath(TEST_TABLE, "$.numValue ? (@ == 10)"),
"Matching documents should have been found")
def byJsonPathNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertFalse(db.conn.existsByJsonPath(TEST_TABLE, "$.numValue ? (@ == 10.1)"),
"Matching documents should not have been found")

View File

@@ -0,0 +1,217 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.Assertions.*
import solutions.bitbadger.documents.{Configuration, Field}
import solutions.bitbadger.documents.scala.extensions.*
import solutions.bitbadger.documents.scala.tests.TEST_TABLE
import scala.jdk.CollectionConverters.*
object FindFunctions:
/** Generate IDs as a pipe-delimited string */
private def docIds(docs: List[JsonDocument]) =
docs.map(_.id).reduce((ids, docId) => s"$ids|$docId")
def allDefault(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(5, db.conn.findAll[JsonDocument](TEST_TABLE).size, "There should have been 5 documents returned")
def allAscending(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val docs = db.conn.findAll[JsonDocument](TEST_TABLE, Field.named("id") :: Nil)
assertEquals(5, docs.size, "There should have been 5 documents returned")
assertEquals("five|four|one|three|two", docIds(docs), "The documents were not ordered correctly")
def allDescending(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val docs = db.conn.findAll[JsonDocument](TEST_TABLE, Field.named("id DESC") :: Nil)
assertEquals(5, docs.size, "There should have been 5 documents returned")
assertEquals("two|three|one|four|five", docIds(docs), "The documents were not ordered correctly")
def allNumOrder(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val docs = db.conn.findAll[JsonDocument](TEST_TABLE,
Field.named("sub.foo NULLS LAST") :: Field.named("n:numValue") :: Nil)
assertEquals(5, docs.size, "There should have been 5 documents returned")
assertEquals("two|four|one|three|five", docIds(docs), "The documents were not ordered correctly")
def allEmpty(db: ThrowawayDatabase): Unit =
assertEquals(0, db.conn.findAll[JsonDocument](TEST_TABLE).size, "There should have been no documents returned")
def byIdString(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val doc = db.conn.findById[String, JsonDocument](TEST_TABLE, "two")
assertTrue(doc.isDefined, "The document should have been returned")
assertEquals("two", doc.get.id, "An incorrect document was returned")
def byIdNumber(db: ThrowawayDatabase): Unit =
Configuration.idField = "key"
try {
db.conn.insert(TEST_TABLE, NumIdDocument(18, "howdy"))
val doc = db.conn.findById[Int, NumIdDocument](TEST_TABLE, 18)
assertTrue(doc.isDefined, "The document should have been returned")
} finally {
Configuration.idField = "id"
}
def byIdNotFound(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertFalse(db.conn.findById[String, JsonDocument](TEST_TABLE, "x").isDefined,
"There should have been no document returned")
def byFieldsMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val docs = db.conn.findByFields[JsonDocument](TEST_TABLE,
Field.any("value", ("blue" :: "purple" :: Nil).asJava) :: Nil, orderBy = Field.exists("sub") :: Nil)
assertEquals(1, docs.size, "There should have been a document returned")
assertEquals("four", docs.head.id, "The incorrect document was returned")
def byFieldsMatchOrdered(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val docs = db.conn.findByFields[JsonDocument](TEST_TABLE, Field.equal("value", "purple") :: Nil,
orderBy = Field.named("id") :: Nil)
assertEquals(2, docs.size, "There should have been 2 documents returned")
assertEquals("five|four", docIds(docs), "The documents were not ordered correctly")
def byFieldsMatchNumIn(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val docs = db.conn.findByFields[JsonDocument](TEST_TABLE,
Field.any("numValue", (2 :: 4 :: 6 :: 8 :: Nil).asJava) :: Nil)
assertEquals(1, docs.size, "There should have been a document returned")
assertEquals("three", docs.head.id, "The incorrect document was returned")
def byFieldsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(0, db.conn.findByFields[JsonDocument](TEST_TABLE, Field.greater("numValue", 100) :: Nil).size,
"There should have been no documents returned")
def byFieldsMatchInArray(db: ThrowawayDatabase): Unit =
ArrayDocument.testDocuments.foreach { doc => db.conn.insert(TEST_TABLE, doc) }
val docs = db.conn.findByFields[ArrayDocument](TEST_TABLE,
Field.inArray("values", TEST_TABLE, ("c" :: Nil).asJava) :: Nil)
assertEquals(2, docs.size, "There should have been two documents returned")
assertTrue(("first" :: "second" :: Nil).contains(docs.head.id),
s"An incorrect document was returned (${docs.head.id}")
assertTrue(("first" :: "second" :: Nil).contains(docs(1).id), s"An incorrect document was returned (${docs(1).id})")
def byFieldsNoMatchInArray(db: ThrowawayDatabase): Unit =
ArrayDocument.testDocuments.foreach { doc => db.conn.insert(TEST_TABLE, doc) }
assertEquals(0,
db.conn.findByFields[ArrayDocument](TEST_TABLE,
Field.inArray("values", TEST_TABLE, ("j" :: Nil).asJava) :: Nil).size,
"There should have been no documents returned")
def byContainsMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val docs = db.conn.findByContains[JsonDocument, Map.Map1[String, String]](TEST_TABLE, Map.Map1("value", "purple"))
assertEquals(2, docs.size, "There should have been 2 documents returned")
assertTrue(("four" :: "five" :: Nil).contains(docs.head.id),
s"An incorrect document was returned (${docs.head.id})")
assertTrue(("four" :: "five" :: Nil).contains(docs(1).id), s"An incorrect document was returned (${docs(1).id})")
def byContainsMatchOrdered(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val docs = db.conn.findByContains[JsonDocument, Map.Map1[String, Map.Map1[String, String]]](TEST_TABLE,
Map.Map1("sub", Map.Map1("foo", "green")), Field.named("value") :: Nil)
assertEquals(2, docs.size, "There should have been 2 documents returned")
assertEquals("two|four", docIds(docs), "The documents were not ordered correctly")
def byContainsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(0,
db.conn.findByContains[JsonDocument, Map.Map1[String, String]](TEST_TABLE, Map.Map1("value", "indigo")).size,
"There should have been no documents returned")
def byJsonPathMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val docs = db.conn.findByJsonPath[JsonDocument](TEST_TABLE, "$.numValue ? (@ > 10)")
assertEquals(2, docs.size, "There should have been 2 documents returned")
assertTrue(("four" :: "five" :: Nil).contains(docs.head.id),
s"An incorrect document was returned (${docs.head.id})")
assertTrue(("four" :: "five" :: Nil).contains(docs(1).id), s"An incorrect document was returned (${docs(1).id})")
def byJsonPathMatchOrdered(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val docs = db.conn.findByJsonPath[JsonDocument](TEST_TABLE, "$.numValue ? (@ > 10)", Field.named("id") :: Nil)
assertEquals(2, docs.size, "There should have been 2 documents returned")
assertEquals("five|four", docIds(docs), "The documents were not ordered correctly")
def byJsonPathNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertEquals(0, db.conn.findByJsonPath[JsonDocument](TEST_TABLE, "$.numValue ? (@ > 100)").size,
"There should have been no documents returned")
def firstByFieldsMatchOne(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val doc = db.conn.findFirstByFields[JsonDocument](TEST_TABLE, Field.equal("value", "another") :: Nil)
assertTrue(doc.isDefined, "There should have been a document returned")
assertEquals("two", doc.get.id, "The incorrect document was returned")
def firstByFieldsMatchMany(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val doc = db.conn.findFirstByFields[JsonDocument](TEST_TABLE, Field.equal("sub.foo", "green") :: Nil)
assertTrue(doc.isDefined, "There should have been a document returned")
assertTrue(("two" :: "four" :: Nil).contains(doc.get.id), s"An incorrect document was returned (${doc.get.id})")
def firstByFieldsMatchOrdered(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val doc = db.conn.findFirstByFields[JsonDocument](TEST_TABLE, Field.equal("sub.foo", "green") :: Nil,
orderBy = Field.named("n:numValue DESC") :: Nil)
assertTrue(doc.isDefined, "There should have been a document returned")
assertEquals("four", doc.get.id, "An incorrect document was returned")
def firstByFieldsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertFalse(db.conn.findFirstByFields[JsonDocument](TEST_TABLE, Field.equal("value", "absent") :: Nil).isDefined,
"There should have been no document returned")
def firstByContainsMatchOne(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val doc = db.conn.findFirstByContains[JsonDocument, Map.Map1[String, String]](TEST_TABLE,
Map.Map1("value", "FIRST!"))
assertTrue(doc.isDefined, "There should have been a document returned")
assertEquals("one", doc.get.id, "An incorrect document was returned")
def firstByContainsMatchMany(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val doc = db.conn.findFirstByContains[JsonDocument, Map.Map1[String, String]](TEST_TABLE,
Map.Map1("value", "purple"))
assertTrue(doc.isDefined, "There should have been a document returned")
assertTrue(("four" :: "five" :: Nil).contains(doc.get.id), s"An incorrect document was returned (${doc.get.id})")
def firstByContainsMatchOrdered(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val doc = db.conn.findFirstByContains[JsonDocument, Map.Map1[String, String]](TEST_TABLE,
Map.Map1("value", "purple"), Field.named("sub.bar NULLS FIRST") :: Nil)
assertTrue(doc.isDefined, "There should have been a document returned")
assertEquals("five", doc.get.id, "An incorrect document was returned")
def firstByContainsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertFalse(db.conn.findFirstByContains[JsonDocument, Map.Map1[String, String]](TEST_TABLE,
Map.Map1("value", "indigo")).isDefined, "There should have been no document returned")
def firstByJsonPathMatchOne(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val doc = db.conn.findFirstByJsonPath[JsonDocument](TEST_TABLE, "$.numValue ? (@ == 10)")
assertTrue(doc.isDefined, "There should have been a document returned")
assertEquals("two", doc.get.id, "An incorrect document was returned")
def firstByJsonPathMatchMany(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val doc = db.conn.findFirstByJsonPath[JsonDocument](TEST_TABLE, "$.numValue ? (@ > 10)")
assertTrue(doc.isDefined, "There should have been a document returned")
assertTrue(("four" :: "five" :: Nil).contains(doc.get.id), s"An incorrect document was returned (${doc.get.id})")
def firstByJsonPathMatchOrdered(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val doc = db.conn.findFirstByJsonPath[JsonDocument](TEST_TABLE, "$.numValue ? (@ > 10)",
Field.named("id DESC") :: Nil)
assertTrue(doc.isDefined, "There should have been a document returned")
assertEquals("four", doc.get.id, "An incorrect document was returned")
def firstByJsonPathNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertFalse(db.conn.findFirstByJsonPath[JsonDocument](TEST_TABLE, "$.numValue ? (@ > 100)").isDefined,
"There should have been no document returned")

View File

@@ -0,0 +1,17 @@
package solutions.bitbadger.documents.scala.tests.integration
import solutions.bitbadger.documents.DocumentSerializer
import com.fasterxml.jackson.databind.ObjectMapper
/**
* A JSON serializer using Jackson's default options
*/
class JacksonDocumentSerializer extends DocumentSerializer:
private val mapper = ObjectMapper()
override def serialize[TDoc](document: TDoc): String =
mapper.writeValueAsString(document)
override def deserialize[TDoc](json: String, clazz: Class[TDoc]): TDoc =
mapper.readValue(json, clazz)

View File

@@ -0,0 +1,19 @@
package solutions.bitbadger.documents.scala.tests.integration
import solutions.bitbadger.documents.scala.extensions.insert
import solutions.bitbadger.documents.scala.tests.TEST_TABLE
class JsonDocument(val id: String = "", val value: String = "", val numValue: Int = 0, val sub: SubDocument = null)
object JsonDocument:
/** Documents to use for testing */
private val testDocuments = List(
JsonDocument("one", "FIRST!", 0, null),
JsonDocument("two", "another", 10, SubDocument("green", "blue")),
JsonDocument("three", "", 4, null),
JsonDocument("four", "purple", 17, SubDocument("green", "red")),
JsonDocument("five", "purple", 18, null))
def load(db: ThrowawayDatabase, tableName: String = TEST_TABLE): Unit =
testDocuments.foreach { it => db.conn.insert(tableName, it) }

View File

@@ -0,0 +1,3 @@
package solutions.bitbadger.documents.scala.tests.integration
class NumIdDocument(val key: Int = 0, val text: String = "")

View File

@@ -0,0 +1,65 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.Assertions.*
import solutions.bitbadger.documents.Field
import solutions.bitbadger.documents.scala.extensions.*
import solutions.bitbadger.documents.scala.tests.TEST_TABLE
object PatchFunctions:
def byIdMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
db.conn.patchById(TEST_TABLE, "one", Map.Map1("numValue", 44))
val doc = db.conn.findById[String, JsonDocument](TEST_TABLE, "one")
assertTrue(doc.isDefined, "There should have been a document returned")
assertEquals("one", doc.get.id, "An incorrect document was returned")
assertEquals(44, doc.get.numValue, "The document was not patched")
def byIdNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertFalse(db.conn.existsById(TEST_TABLE, "forty-seven"), "Document with ID \"forty-seven\" should not exist")
db.conn.patchById(TEST_TABLE, "forty-seven", Map.Map1("foo", "green")) // no exception = pass
def byFieldsMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
db.conn.patchByFields(TEST_TABLE, Field.equal("value", "purple") :: Nil, Map.Map1("numValue", 77))
assertEquals(2L, db.conn.countByFields(TEST_TABLE, Field.equal("numValue", 77) :: Nil),
"There should have been 2 documents with numeric value 77")
def byFieldsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val fields = Field.equal("value", "burgundy") :: Nil
assertFalse(db.conn.existsByFields(TEST_TABLE, fields), "There should be no documents with value of \"burgundy\"")
db.conn.patchByFields(TEST_TABLE, fields, Map.Map1("foo", "green")) // no exception = pass
def byContainsMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val contains = Map.Map1("value", "another")
db.conn.patchByContains(TEST_TABLE, contains, Map.Map1("numValue", 12))
val doc = db.conn.findFirstByContains[JsonDocument, Map.Map1[String, String]](TEST_TABLE, contains)
assertTrue(doc.isDefined, "There should have been a document returned")
assertEquals("two", doc.get.id, "The incorrect document was returned")
assertEquals(12, doc.get.numValue, "The document was not updated")
def byContainsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val contains = Map.Map1("value", "updated")
assertFalse(db.conn.existsByContains(TEST_TABLE, contains), "There should be no matching documents")
db.conn.patchByContains(TEST_TABLE, contains, Map.Map1("sub.foo", "green")) // no exception = pass
def byJsonPathMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val path = "$.numValue ? (@ > 10)"
db.conn.patchByJsonPath(TEST_TABLE, path, Map.Map1("value", "blue"))
val docs = db.conn.findByJsonPath[JsonDocument](TEST_TABLE, path)
assertEquals(2, docs.size, "There should have been two documents returned")
docs.foreach { doc =>
assertTrue(("four" :: "five" :: Nil).contains(doc.id), s"An incorrect document was returned (${doc.id})")
assertEquals("blue", doc.value, s"The value for ID ${doc.id} was incorrect")
}
def byJsonPathNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val path = "$.numValue ? (@ > 100)"
assertFalse(db.conn.existsByJsonPath(TEST_TABLE, path), "There should be no documents with numeric values over 100")
db.conn.patchByJsonPath(TEST_TABLE, path, Map.Map1("value", "blue")) // no exception = pass

View File

@@ -0,0 +1,45 @@
package solutions.bitbadger.documents.scala.tests.integration
import solutions.bitbadger.documents.{Configuration, Parameter, ParameterType}
import solutions.bitbadger.documents.scala.Results
import solutions.bitbadger.documents.scala.extensions.*
import solutions.bitbadger.documents.scala.tests.TEST_TABLE
import java.sql.Connection
import scala.util.Using
/**
* A wrapper for a throwaway PostgreSQL database
*/
class PgDB extends ThrowawayDatabase:
Configuration.setConnectionString(PgDB.connString("postgres"))
Using(Configuration.dbConn()) { conn => conn.customNonQuery(s"CREATE DATABASE $dbName") }
Configuration.setConnectionString(PgDB.connString(dbName))
override val conn: Connection = Configuration.dbConn()
conn.ensureTable(TEST_TABLE)
override def close(): Unit =
conn.close()
Configuration.setConnectionString(PgDB.connString("postgres"))
Using(Configuration.dbConn()) { conn => conn.customNonQuery(s"DROP DATABASE $dbName") }
Configuration.setConnectionString(null)
override def dbObjectExists(name: String): Boolean =
conn.customScalar("SELECT EXISTS (SELECT 1 FROM pg_class WHERE relname = :name) AS it",
Parameter(":name", ParameterType.STRING, name) :: Nil, Results.toExists)
object PgDB:
/**
* Create a connection string for the given database
*
* @param database The database to which the library should connect
* @return The connection string for the database
*/
private def connString(database: String): String =
s"jdbc:postgresql://localhost/$database?user=postgres&password=postgres"

View File

@@ -0,0 +1,43 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.{DisplayName, Test}
import scala.util.Using
@DisplayName("Scala | PostgreSQL: Count")
class PostgreSQLCountIT:
@Test
@DisplayName("all counts all documents")
def all(): Unit =
Using(PgDB()) { db => CountFunctions.all(db) }
@Test
@DisplayName("byFields counts documents by a numeric value")
def byFieldsNumeric(): Unit =
Using(PgDB()) { db => CountFunctions.byFieldsNumeric(db) }
@Test
@DisplayName("byFields counts documents by a alphanumeric value")
def byFieldsAlpha(): Unit =
Using(PgDB()) { db => CountFunctions.byFieldsAlpha(db) }
@Test
@DisplayName("byContains counts documents when matches are found")
def byContainsMatch(): Unit =
Using(PgDB()) { db => CountFunctions.byContainsMatch(db) }
@Test
@DisplayName("byContains counts documents when no matches are found")
def byContainsNoMatch(): Unit =
Using(PgDB()) { db => CountFunctions.byContainsNoMatch(db) }
@Test
@DisplayName("byJsonPath counts documents when matches are found")
def byJsonPathMatch(): Unit =
Using(PgDB()) { db => CountFunctions.byJsonPathMatch(db) }
@Test
@DisplayName("byJsonPath counts documents when no matches are found")
def byJsonPathNoMatch(): Unit =
Using(PgDB()) { db => CountFunctions.byJsonPathNoMatch(db) }

View File

@@ -0,0 +1,43 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.{DisplayName, Test}
import scala.util.Using
@DisplayName("Scala | PostgreSQL: Custom")
class PostgreSQLCustomIT:
@Test
@DisplayName("list succeeds with empty list")
def listEmpty(): Unit =
Using(PgDB()) { db => CustomFunctions.listEmpty(db) }
@Test
@DisplayName("list succeeds with a non-empty list")
def listAll(): Unit =
Using(PgDB()) { db => CustomFunctions.listAll(db) }
@Test
@DisplayName("single succeeds when document not found")
def singleNone(): Unit =
Using(PgDB()) { db => CustomFunctions.singleNone(db) }
@Test
@DisplayName("single succeeds when a document is found")
def singleOne(): Unit =
Using(PgDB()) { db => CustomFunctions.singleOne(db) }
@Test
@DisplayName("nonQuery makes changes")
def nonQueryChanges(): Unit =
Using(PgDB()) { db => CustomFunctions.nonQueryChanges(db) }
@Test
@DisplayName("nonQuery makes no changes when where clause matches nothing")
def nonQueryNoChanges(): Unit =
Using(PgDB()) { db => CustomFunctions.nonQueryNoChanges(db) }
@Test
@DisplayName("scalar succeeds")
def scalar(): Unit =
Using(PgDB()) { db => CustomFunctions.scalar(db) }

View File

@@ -0,0 +1,31 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.{DisplayName, Test}
import scala.util.Using
/**
* PostgreSQL integration tests for the `Definition` object / `ensure*` connection extension functions
*/
@DisplayName("Scala | PostgreSQL: Definition")
class PostgreSQLDefinitionIT:
@Test
@DisplayName("ensureTable creates table and index")
def ensureTable(): Unit =
Using(PgDB()) { db => DefinitionFunctions.ensureTable(db) }
@Test
@DisplayName("ensureFieldIndex creates an index")
def ensureFieldIndex(): Unit =
Using(PgDB()) { db => DefinitionFunctions.ensureFieldIndex(db) }
@Test
@DisplayName("ensureDocumentIndex creates a full index")
def ensureDocumentIndexFull(): Unit =
Using(PgDB()) { db => DefinitionFunctions.ensureDocumentIndexFull(db) }
@Test
@DisplayName("ensureDocumentIndex creates an optimized index")
def ensureDocumentIndexOptimized(): Unit =
Using(PgDB()) { db => DefinitionFunctions.ensureDocumentIndexOptimized(db) }

View File

@@ -0,0 +1,51 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.{DisplayName, Test}
import scala.util.Using
/**
* PostgreSQL integration tests for the `Delete` object / `deleteBy*` connection extension functions
*/
@DisplayName("Scala | PostgreSQL: Delete")
class PostgreSQLDeleteIT:
@Test
@DisplayName("byId deletes a matching ID")
def byIdMatch(): Unit =
Using(PgDB()) { db => DeleteFunctions.byIdMatch(db) }
@Test
@DisplayName("byId succeeds when no ID matches")
def byIdNoMatch(): Unit =
Using(PgDB()) { db => DeleteFunctions.byIdNoMatch(db) }
@Test
@DisplayName("byFields deletes matching documents")
def byFieldsMatch(): Unit =
Using(PgDB()) { db => DeleteFunctions.byFieldsMatch(db) }
@Test
@DisplayName("byFields succeeds when no documents match")
def byFieldsNoMatch(): Unit =
Using(PgDB()) { db => DeleteFunctions.byFieldsNoMatch(db) }
@Test
@DisplayName("byContains deletes matching documents")
def byContainsMatch(): Unit =
Using(PgDB()) { db => DeleteFunctions.byContainsMatch(db) }
@Test
@DisplayName("byContains succeeds when no documents match")
def byContainsNoMatch(): Unit =
Using(PgDB()) { db => DeleteFunctions.byContainsNoMatch(db) }
@Test
@DisplayName("byJsonPath deletes matching documents")
def byJsonPathMatch(): Unit =
Using(PgDB()) { db => DeleteFunctions.byJsonPathMatch(db) }
@Test
@DisplayName("byJsonPath succeeds when no documents match")
def byJsonPathNoMatch(): Unit =
Using(PgDB()) { db => DeleteFunctions.byJsonPathNoMatch(db) }

View File

@@ -0,0 +1,56 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.{DisplayName, Test}
import scala.util.Using
/**
* PostgreSQL integration tests for the `Document` object / `insert`, `save`, `update` connection extension functions
*/
@DisplayName("Scala | PostgreSQL: Document")
class PostgreSQLDocumentIT:
@Test
@DisplayName("insert works with default values")
def insertDefault(): Unit =
Using(PgDB()) { db => DocumentFunctions.insertDefault(db) }
@Test
@DisplayName("insert fails with duplicate key")
def insertDupe(): Unit =
Using(PgDB()) { db => DocumentFunctions.insertDupe(db) }
@Test
@DisplayName("insert succeeds with numeric auto IDs")
def insertNumAutoId(): Unit =
Using(PgDB()) { db => DocumentFunctions.insertNumAutoId(db) }
@Test
@DisplayName("insert succeeds with UUID auto ID")
def insertUUIDAutoId(): Unit =
Using(PgDB()) { db => DocumentFunctions.insertUUIDAutoId(db) }
@Test
@DisplayName("insert succeeds with random string auto ID")
def insertStringAutoId(): Unit =
Using(PgDB()) { db => DocumentFunctions.insertStringAutoId(db) }
@Test
@DisplayName("save updates an existing document")
def saveMatch(): Unit =
Using(PgDB()) { db => DocumentFunctions.saveMatch(db) }
@Test
@DisplayName("save inserts a new document")
def saveNoMatch(): Unit =
Using(PgDB()) { db => DocumentFunctions.saveNoMatch(db) }
@Test
@DisplayName("update replaces an existing document")
def updateMatch(): Unit =
Using(PgDB()) { db => DocumentFunctions.updateMatch(db) }
@Test
@DisplayName("update succeeds when no document exists")
def updateNoMatch(): Unit =
Using(PgDB()) { db => DocumentFunctions.updateNoMatch(db) }

View File

@@ -0,0 +1,51 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.{DisplayName, Test}
import scala.util.Using
/**
* PostgreSQL integration tests for the `Exists` object / `existsBy*` connection extension functions
*/
@DisplayName("Scala | PostgreSQL: Exists")
class PostgreSQLExistsIT:
@Test
@DisplayName("byId returns true when a document matches the ID")
def byIdMatch(): Unit =
Using(PgDB()) { db => ExistsFunctions.byIdMatch(db) }
@Test
@DisplayName("byId returns false when no document matches the ID")
def byIdNoMatch(): Unit =
Using(PgDB()) { db => ExistsFunctions.byIdNoMatch(db) }
@Test
@DisplayName("byFields returns true when documents match")
def byFieldsMatch(): Unit =
Using(PgDB()) { db => ExistsFunctions.byFieldsMatch(db) }
@Test
@DisplayName("byFields returns false when no documents match")
def byFieldsNoMatch(): Unit =
Using(PgDB()) { db => ExistsFunctions.byFieldsNoMatch(db) }
@Test
@DisplayName("byContains returns true when documents match")
def byContainsMatch(): Unit =
Using(PgDB()) { db => ExistsFunctions.byContainsMatch(db) }
@Test
@DisplayName("byContains returns false when no documents match")
def byContainsNoMatch(): Unit =
Using(PgDB()) { db => ExistsFunctions.byContainsNoMatch(db) }
@Test
@DisplayName("byJsonPath returns true when documents match")
def byJsonPathMatch(): Unit =
Using(PgDB()) { db => ExistsFunctions.byJsonPathMatch(db) }
@Test
@DisplayName("byJsonPath returns false when no documents match")
def byJsonPathNoMatch(): Unit =
Using(PgDB()) { db => ExistsFunctions.byJsonPathNoMatch(db) }

View File

@@ -0,0 +1,172 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.{DisplayName, Test}
import scala.util.Using
/**
* PostgreSQL integration tests for the `Find` object / `find*` connection extension functions
*/
@DisplayName("Scala | PostgreSQL: Find")
class PostgreSQLFindIT:
@Test
@DisplayName("all retrieves all documents")
def allDefault(): Unit =
Using(PgDB()) { db => FindFunctions.allDefault(db) }
@Test
@DisplayName("all sorts data ascending")
def allAscending(): Unit =
Using(PgDB()) { db => FindFunctions.allAscending(db) }
@Test
@DisplayName("all sorts data descending")
def allDescending(): Unit =
Using(PgDB()) { db => FindFunctions.allDescending(db) }
@Test
@DisplayName("all sorts data numerically")
def allNumOrder(): Unit =
Using(PgDB()) { db => FindFunctions.allNumOrder(db) }
@Test
@DisplayName("all succeeds with an empty table")
def allEmpty(): Unit =
Using(PgDB()) { db => FindFunctions.allEmpty(db) }
@Test
@DisplayName("byId retrieves a document via a string ID")
def byIdString(): Unit =
Using(PgDB()) { db => FindFunctions.byIdString(db) }
@Test
@DisplayName("byId retrieves a document via a numeric ID")
def byIdNumber(): Unit =
Using(PgDB()) { db => FindFunctions.byIdNumber(db) }
@Test
@DisplayName("byId returns null when a matching ID is not found")
def byIdNotFound(): Unit =
Using(PgDB()) { db => FindFunctions.byIdNotFound(db) }
@Test
@DisplayName("byFields retrieves matching documents")
def byFieldsMatch(): Unit =
Using(PgDB()) { db => FindFunctions.byFieldsMatch(db) }
@Test
@DisplayName("byFields retrieves ordered matching documents")
def byFieldsMatchOrdered(): Unit =
Using(PgDB()) { db => FindFunctions.byFieldsMatchOrdered(db) }
@Test
@DisplayName("byFields retrieves matching documents with a numeric IN clause")
def byFieldsMatchNumIn(): Unit =
Using(PgDB()) { db => FindFunctions.byFieldsMatchNumIn(db) }
@Test
@DisplayName("byFields succeeds when no documents match")
def byFieldsNoMatch(): Unit =
Using(PgDB()) { db => FindFunctions.byFieldsNoMatch(db) }
@Test
@DisplayName("byFields retrieves matching documents with an IN_ARRAY comparison")
def byFieldsMatchInArray(): Unit =
Using(PgDB()) { db => FindFunctions.byFieldsMatchInArray(db) }
@Test
@DisplayName("byFields succeeds when no documents match an IN_ARRAY comparison")
def byFieldsNoMatchInArray(): Unit =
Using(PgDB()) { db => FindFunctions.byFieldsNoMatchInArray(db) }
@Test
@DisplayName("byContains retrieves matching documents")
def byContainsMatch(): Unit =
Using(PgDB()) { db => FindFunctions.byContainsMatch(db) }
@Test
@DisplayName("byContains retrieves ordered matching documents")
def byContainsMatchOrdered(): Unit =
Using(PgDB()) { db => FindFunctions.byContainsMatchOrdered(db) }
@Test
@DisplayName("byContains succeeds when no documents match")
def byContainsNoMatch(): Unit =
Using(PgDB()) { db => FindFunctions.byContainsNoMatch(db) }
@Test
@DisplayName("byJsonPath retrieves matching documents")
def byJsonPathMatch(): Unit =
Using(PgDB()) { db => FindFunctions.byJsonPathMatch(db) }
@Test
@DisplayName("byJsonPath retrieves ordered matching documents")
def byJsonPathMatchOrdered(): Unit =
Using(PgDB()) { db => FindFunctions.byJsonPathMatchOrdered(db) }
@Test
@DisplayName("byJsonPath succeeds when no documents match")
def byJsonPathNoMatch(): Unit =
Using(PgDB()) { db => FindFunctions.byJsonPathNoMatch(db) }
@Test
@DisplayName("firstByFields retrieves a matching document")
def firstByFieldsMatchOne(): Unit =
Using(PgDB()) { db => FindFunctions.firstByFieldsMatchOne(db) }
@Test
@DisplayName("firstByFields retrieves a matching document among many")
def firstByFieldsMatchMany(): Unit =
Using(PgDB()) { db => FindFunctions.firstByFieldsMatchMany(db) }
@Test
@DisplayName("firstByFields retrieves a matching document among many (ordered)")
def firstByFieldsMatchOrdered(): Unit =
Using(PgDB()) { db => FindFunctions.firstByFieldsMatchOrdered(db) }
@Test
@DisplayName("firstByFields returns null when no document matches")
def firstByFieldsNoMatch(): Unit =
Using(PgDB()) { db => FindFunctions.firstByFieldsNoMatch(db) }
@Test
@DisplayName("firstByContains retrieves a matching document")
def firstByContainsMatchOne(): Unit =
Using(PgDB()) { db => FindFunctions.firstByContainsMatchOne(db) }
@Test
@DisplayName("firstByContains retrieves a matching document among many")
def firstByContainsMatchMany(): Unit =
Using(PgDB()) { db => FindFunctions.firstByContainsMatchMany(db) }
@Test
@DisplayName("firstByContains retrieves a matching document among many (ordered)")
def firstByContainsMatchOrdered(): Unit =
Using(PgDB()) { db => FindFunctions.firstByContainsMatchOrdered(db) }
@Test
@DisplayName("firstByContains returns null when no document matches")
def firstByContainsNoMatch(): Unit =
Using(PgDB()) { db => FindFunctions.firstByContainsNoMatch(db) }
@Test
@DisplayName("firstByJsonPath retrieves a matching document")
def firstByJsonPathMatchOne(): Unit =
Using(PgDB()) { db => FindFunctions.firstByJsonPathMatchOne(db) }
@Test
@DisplayName("firstByJsonPath retrieves a matching document among many")
def firstByJsonPathMatchMany(): Unit =
Using(PgDB()) { db => FindFunctions.firstByJsonPathMatchMany(db) }
@Test
@DisplayName("firstByJsonPath retrieves a matching document among many (ordered)")
def firstByJsonPathMatchOrdered(): Unit =
Using(PgDB()) { db => FindFunctions.firstByJsonPathMatchOrdered(db) }
@Test
@DisplayName("firstByJsonPath returns null when no document matches")
def firstByJsonPathNoMatch(): Unit =
Using(PgDB()) { db => FindFunctions.firstByJsonPathNoMatch(db) }

View File

@@ -0,0 +1,51 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.{DisplayName, Test}
import scala.util.Using
/**
* PostgreSQL integration tests for the `Patch` object / `patchBy*` connection extension functions
*/
@DisplayName("Scala | PostgreSQL: Patch")
class PostgreSQLPatchIT:
@Test
@DisplayName("byId patches an existing document")
def byIdMatch(): Unit =
Using(PgDB()) { db => PatchFunctions. byIdMatch(db) }
@Test
@DisplayName("byId succeeds for a non-existent document")
def byIdNoMatch(): Unit =
Using(PgDB()) { db => PatchFunctions. byIdNoMatch(db) }
@Test
@DisplayName("byFields patches matching document")
def byFieldsMatch(): Unit =
Using(PgDB()) { db => PatchFunctions. byFieldsMatch(db) }
@Test
@DisplayName("byFields succeeds when no documents match")
def byFieldsNoMatch(): Unit =
Using(PgDB()) { db => PatchFunctions. byFieldsNoMatch(db) }
@Test
@DisplayName("byContains patches matching document")
def byContainsMatch(): Unit =
Using(PgDB()) { db => PatchFunctions. byContainsMatch(db) }
@Test
@DisplayName("byContains succeeds when no documents match")
def byContainsNoMatch(): Unit =
Using(PgDB()) { db => PatchFunctions. byContainsNoMatch(db) }
@Test
@DisplayName("byJsonPath patches matching document")
def byJsonPathMatch(): Unit =
Using(PgDB()) { db => PatchFunctions. byJsonPathMatch(db) }
@Test
@DisplayName("byJsonPath succeeds when no documents match")
def byJsonPathNoMatch(): Unit =
Using(PgDB()) { db => PatchFunctions. byJsonPathNoMatch(db) }

View File

@@ -0,0 +1,71 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.{DisplayName, Test}
import scala.util.Using
/**
* PostgreSQL integration tests for the `RemoveFields` object / `removeFieldsBy*` connection extension functions
*/
@DisplayName("Scala | PostgreSQL: RemoveFields")
class PostgreSQLRemoveFieldsIT:
@Test
@DisplayName("byId removes fields from an existing document")
def byIdMatchFields(): Unit =
Using(PgDB()) { db => RemoveFieldsFunctions. byIdMatchFields(db) }
@Test
@DisplayName("byId succeeds when fields do not exist on an existing document")
def byIdMatchNoFields(): Unit =
Using(PgDB()) { db => RemoveFieldsFunctions. byIdMatchNoFields(db) }
@Test
@DisplayName("byId succeeds when no document exists")
def byIdNoMatch(): Unit =
Using(PgDB()) { db => RemoveFieldsFunctions. byIdNoMatch(db) }
@Test
@DisplayName("byFields removes fields from matching documents")
def byFieldsMatchFields(): Unit =
Using(PgDB()) { db => RemoveFieldsFunctions. byFieldsMatchFields(db) }
@Test
@DisplayName("byFields succeeds when fields do not exist on matching documents")
def byFieldsMatchNoFields(): Unit =
Using(PgDB()) { db => RemoveFieldsFunctions. byFieldsMatchNoFields(db) }
@Test
@DisplayName("byFields succeeds when no matching documents exist")
def byFieldsNoMatch(): Unit =
Using(PgDB()) { db => RemoveFieldsFunctions. byFieldsNoMatch(db) }
@Test
@DisplayName("byContains removes fields from matching documents")
def byContainsMatchFields(): Unit =
Using(PgDB()) { db => RemoveFieldsFunctions. byContainsMatchFields(db) }
@Test
@DisplayName("byContains succeeds when fields do not exist on matching documents")
def byContainsMatchNoFields(): Unit =
Using(PgDB()) { db => RemoveFieldsFunctions. byContainsMatchNoFields(db) }
@Test
@DisplayName("byContains succeeds when no matching documents exist")
def byContainsNoMatch(): Unit =
Using(PgDB()) { db => RemoveFieldsFunctions. byContainsNoMatch(db) }
@Test
@DisplayName("byJsonPath removes fields from matching documents")
def byJsonPathMatchFields(): Unit =
Using(PgDB()) { db => RemoveFieldsFunctions. byJsonPathMatchFields(db) }
@Test
@DisplayName("byJsonPath succeeds when fields do not exist on matching documents")
def byJsonPathMatchNoFields(): Unit =
Using(PgDB()) { db => RemoveFieldsFunctions. byJsonPathMatchNoFields(db) }
@Test
@DisplayName("byJsonPath succeeds when no matching documents exist")
def byJsonPathNoMatch(): Unit =
Using(PgDB()) { db => RemoveFieldsFunctions. byJsonPathNoMatch(db) }

View File

@@ -0,0 +1,92 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.Assertions.*
import solutions.bitbadger.documents.Field
import solutions.bitbadger.documents.scala.extensions.*
import solutions.bitbadger.documents.scala.tests.TEST_TABLE
object RemoveFieldsFunctions:
def byIdMatchFields(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
db.conn.removeFieldsById(TEST_TABLE, "two", "sub" :: "value" :: Nil)
val doc = db.conn.findById[String, JsonDocument](TEST_TABLE, "two")
assertTrue(doc.isDefined, "There should have been a document returned")
assertEquals("", doc.get.value, "The value should have been empty")
assertNull(doc.get.sub, "The sub-document should have been removed")
def byIdMatchNoFields(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertFalse(db.conn.existsByFields(TEST_TABLE, Field.exists("a_field_that_does_not_exist") :: Nil))
db.conn.removeFieldsById(TEST_TABLE, "one", "a_field_that_does_not_exist" :: Nil) // no exception = pass
def byIdNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertFalse(db.conn.existsById(TEST_TABLE, "fifty"))
db.conn.removeFieldsById(TEST_TABLE, "fifty", "sub" :: Nil) // no exception = pass
def byFieldsMatchFields(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val fields = Field.equal("numValue", 17) :: Nil
db.conn.removeFieldsByFields(TEST_TABLE, fields, "sub" :: Nil)
val doc = db.conn.findFirstByFields[JsonDocument](TEST_TABLE, fields)
assertTrue(doc.isDefined, "The document should have been returned")
assertEquals("four", doc.get.id, "An incorrect document was returned")
assertNull(doc.get.sub, "The sub-document should have been removed")
def byFieldsMatchNoFields(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertFalse(db.conn.existsByFields(TEST_TABLE, Field.exists("nada") :: Nil))
db.conn.removeFieldsByFields(TEST_TABLE, Field.equal("numValue", 17) :: Nil, "nada" :: Nil) // no exn = pass
def byFieldsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val fields = Field.notEqual("missing", "nope") :: Nil
assertFalse(db.conn.existsByFields(TEST_TABLE, fields))
db.conn.removeFieldsByFields(TEST_TABLE, fields, "value" :: Nil) // no exception = pass
def byContainsMatchFields(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val criteria = Map.Map1("sub", Map.Map1("foo", "green"))
db.conn.removeFieldsByContains(TEST_TABLE, criteria, "value" :: Nil)
val docs = db.conn.findByContains[JsonDocument, Map.Map1[String, Map.Map1[String, String]]](TEST_TABLE, criteria)
assertEquals(2, docs.size, "There should have been 2 documents returned")
docs.foreach { doc =>
assertTrue(("two" :: "four" :: Nil).contains(doc.id), s"An incorrect document was returned (${doc.id})")
assertEquals("", doc.value, "The value should have been empty")
}
def byContainsMatchNoFields(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertFalse(db.conn.existsByFields(TEST_TABLE, Field.exists("invalid_field") :: Nil))
db.conn.removeFieldsByContains(TEST_TABLE, Map.Map1("sub", Map.Map1("foo", "green")), "invalid_field" :: Nil)
// no exception = pass
def byContainsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val contains = Map.Map1("value", "substantial")
assertFalse(db.conn.existsByContains(TEST_TABLE, contains))
db.conn.removeFieldsByContains(TEST_TABLE, contains, "numValue" :: Nil)
def byJsonPathMatchFields(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val path = "$.value ? (@ == \"purple\")"
db.conn.removeFieldsByJsonPath(TEST_TABLE, path, "sub" :: Nil)
val docs = db.conn.findByJsonPath[JsonDocument](TEST_TABLE, path)
assertEquals(2, docs.size, "There should have been 2 documents returned")
docs.foreach { doc =>
assertTrue(("four" :: "five" :: Nil).contains(doc.id), s"An incorrect document was returned (${doc.id})")
assertNull(doc.sub, "The sub-document should have been removed")
}
def byJsonPathMatchNoFields(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
assertFalse(db.conn.existsByFields(TEST_TABLE, Field.exists("submarine") :: Nil))
db.conn.removeFieldsByJsonPath(TEST_TABLE, "$.value ? (@ == \"purple\")", "submarine" :: Nil) // no exn = pass
def byJsonPathNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val path = "$.value ? (@ == \"mauve\")"
assertFalse(db.conn.existsByJsonPath(TEST_TABLE, path))
db.conn.removeFieldsByJsonPath(TEST_TABLE, path, "value" :: Nil) // no exception = pass

View File

@@ -0,0 +1,35 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.{DisplayName, Test}
import solutions.bitbadger.documents.DocumentException
import scala.util.Using
@DisplayName("Scala | SQLite: Count")
class SQLiteCountIT:
@Test
@DisplayName("all counts all documents")
def all(): Unit =
Using(SQLiteDB()) { db => CountFunctions.all(db) }
@Test
@DisplayName("byFields counts documents by a numeric value")
def byFieldsNumeric(): Unit =
Using(SQLiteDB()) { db => CountFunctions.byFieldsNumeric(db) }
@Test
@DisplayName("byFields counts documents by a alphanumeric value")
def byFieldsAlpha(): Unit =
Using(SQLiteDB()) { db => CountFunctions.byFieldsAlpha(db) }
@Test
@DisplayName("byContains fails")
def byContainsMatch(): Unit =
Using(SQLiteDB()) { db => assertThrows(classOf[DocumentException], () => CountFunctions.byContainsMatch(db)) }
@Test
@DisplayName("byJsonPath fails")
def byJsonPathMatch(): Unit =
Using(SQLiteDB()) { db => assertThrows(classOf[DocumentException], () => CountFunctions.byJsonPathMatch(db)) }

View File

@@ -0,0 +1,43 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.{DisplayName, Test}
import scala.util.Using
@DisplayName("Scala | SQLite: Custom")
class SQLiteCustomIT:
@Test
@DisplayName("list succeeds with empty list")
def listEmpty(): Unit =
Using(SQLiteDB()) { db => CustomFunctions.listEmpty(db) }
@Test
@DisplayName("list succeeds with a non-empty list")
def listAll(): Unit =
Using(SQLiteDB()) { db => CustomFunctions.listAll(db) }
@Test
@DisplayName("single succeeds when document not found")
def singleNone(): Unit =
Using(SQLiteDB()) { db => CustomFunctions.singleNone(db) }
@Test
@DisplayName("single succeeds when a document is found")
def singleOne(): Unit =
Using(SQLiteDB()) { db => CustomFunctions.singleOne(db) }
@Test
@DisplayName("nonQuery makes changes")
def nonQueryChanges(): Unit =
Using(SQLiteDB()) { db => CustomFunctions.nonQueryChanges(db) }
@Test
@DisplayName("nonQuery makes no changes when where clause matches nothing")
def nonQueryNoChanges(): Unit =
Using(SQLiteDB()) { db => CustomFunctions.nonQueryNoChanges(db) }
@Test
@DisplayName("scalar succeeds")
def scalar(): Unit =
Using(SQLiteDB()) { db => CustomFunctions.scalar(db) }

View File

@@ -0,0 +1,30 @@
package solutions.bitbadger.documents.scala.tests.integration
import solutions.bitbadger.documents.{Configuration, Parameter, ParameterType}
import solutions.bitbadger.documents.scala.Results
import solutions.bitbadger.documents.scala.extensions.*
import solutions.bitbadger.documents.scala.tests.TEST_TABLE
import java.io.File
import java.sql.Connection
/**
* A wrapper for a throwaway SQLite database
*/
class SQLiteDB extends ThrowawayDatabase:
Configuration.setConnectionString(s"jdbc:sqlite:$dbName.db")
override val conn: Connection = Configuration.dbConn()
conn.ensureTable(TEST_TABLE)
override def close(): Unit =
conn.close()
File(s"$dbName.db").delete()
Configuration.setConnectionString(null)
override def dbObjectExists(name: String): Boolean =
conn.customScalar[Boolean]("SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE name = :name) AS it",
Parameter(":name", ParameterType.STRING, name) :: Nil, Results.toExists)

View File

@@ -0,0 +1,30 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.{DisplayName, Test}
import org.junit.jupiter.api.Assertions.assertThrows
import solutions.bitbadger.documents.DocumentException
import scala.util.Using
/**
* SQLite integration tests for the `Definition` object / `ensure*` connection extension functions
*/
@DisplayName("Scala | SQLite: Definition")
class SQLiteDefinitionIT:
@Test
@DisplayName("ensureTable creates table and index")
def ensureTable(): Unit =
Using(SQLiteDB()) { db => DefinitionFunctions.ensureTable(db) }
@Test
@DisplayName("ensureFieldIndex creates an index")
def ensureFieldIndex(): Unit =
Using(SQLiteDB()) { db => DefinitionFunctions.ensureFieldIndex(db) }
@Test
@DisplayName("ensureDocumentIndex creates a full index")
def ensureDocumentIndexFull(): Unit =
Using(SQLiteDB()) { db =>
assertThrows(classOf[DocumentException], () => DefinitionFunctions.ensureDocumentIndexFull(db))
}

View File

@@ -0,0 +1,43 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.{DisplayName, Test}
import org.junit.jupiter.api.Assertions.assertThrows
import solutions.bitbadger.documents.DocumentException
import scala.util.Using
/**
* SQLite integration tests for the `Delete` object / `deleteBy*` connection extension functions
*/
@DisplayName("Scala | SQLite: Delete")
class SQLiteDeleteIT:
@Test
@DisplayName("byId deletes a matching ID")
def byIdMatch(): Unit =
Using(SQLiteDB()) { db => DeleteFunctions.byIdMatch(db) }
@Test
@DisplayName("byId succeeds when no ID matches")
def byIdNoMatch(): Unit =
Using(SQLiteDB()) { db => DeleteFunctions.byIdNoMatch(db) }
@Test
@DisplayName("byFields deletes matching documents")
def byFieldsMatch(): Unit =
Using(SQLiteDB()) { db => DeleteFunctions.byFieldsMatch(db) }
@Test
@DisplayName("byFields succeeds when no documents match")
def byFieldsNoMatch(): Unit =
Using(SQLiteDB()) { db => DeleteFunctions.byFieldsNoMatch(db) }
@Test
@DisplayName("byContains fails")
def byContainsFails(): Unit =
Using(SQLiteDB()) { db => assertThrows(classOf[DocumentException], () => DeleteFunctions.byContainsMatch(db)) }
@Test
@DisplayName("byJsonPath fails")
def byJsonPathFails(): Unit =
Using(SQLiteDB()) { db => assertThrows(classOf[DocumentException], () => DeleteFunctions.byJsonPathMatch(db)) }

View File

@@ -0,0 +1,56 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.{DisplayName, Test}
import scala.util.Using
/**
* SQLite integration tests for the `Document` object / `insert`, `save`, `update` connection extension functions
*/
@DisplayName("Scala | SQLite: Document")
class SQLiteDocumentIT:
@Test
@DisplayName("insert works with default values")
def insertDefault(): Unit =
Using(SQLiteDB()) { db => DocumentFunctions.insertDefault(db) }
@Test
@DisplayName("insert fails with duplicate key")
def insertDupe(): Unit =
Using(SQLiteDB()) { db => DocumentFunctions.insertDupe(db) }
@Test
@DisplayName("insert succeeds with numeric auto IDs")
def insertNumAutoId(): Unit =
Using(SQLiteDB()) { db => DocumentFunctions.insertNumAutoId(db) }
@Test
@DisplayName("insert succeeds with UUID auto ID")
def insertUUIDAutoId(): Unit =
Using(SQLiteDB()) { db => DocumentFunctions.insertUUIDAutoId(db) }
@Test
@DisplayName("insert succeeds with random string auto ID")
def insertStringAutoId(): Unit =
Using(SQLiteDB()) { db => DocumentFunctions.insertStringAutoId(db) }
@Test
@DisplayName("save updates an existing document")
def saveMatch(): Unit =
Using(SQLiteDB()) { db => DocumentFunctions.saveMatch(db) }
@Test
@DisplayName("save inserts a new document")
def saveNoMatch(): Unit =
Using(SQLiteDB()) { db => DocumentFunctions.saveNoMatch(db) }
@Test
@DisplayName("update replaces an existing document")
def updateMatch(): Unit =
Using(SQLiteDB()) { db => DocumentFunctions.updateMatch(db) }
@Test
@DisplayName("update succeeds when no document exists")
def updateNoMatch(): Unit =
Using(SQLiteDB()) { db => DocumentFunctions.updateNoMatch(db) }

View File

@@ -0,0 +1,43 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.{DisplayName, Test}
import org.junit.jupiter.api.Assertions.assertThrows
import solutions.bitbadger.documents.DocumentException
import scala.util.Using
/**
* SQLite integration tests for the `Exists` object / `existsBy*` connection extension functions
*/
@DisplayName("Scala | SQLite: Exists")
class SQLiteExistsIT:
@Test
@DisplayName("byId returns true when a document matches the ID")
def byIdMatch(): Unit =
Using(SQLiteDB()) { db => ExistsFunctions.byIdMatch(db) }
@Test
@DisplayName("byId returns false when no document matches the ID")
def byIdNoMatch(): Unit =
Using(SQLiteDB()) { db => ExistsFunctions.byIdNoMatch(db) }
@Test
@DisplayName("byFields returns true when documents match")
def byFieldsMatch(): Unit =
Using(SQLiteDB()) { db => ExistsFunctions.byFieldsMatch(db) }
@Test
@DisplayName("byFields returns false when no documents match")
def byFieldsNoMatch(): Unit =
Using(SQLiteDB()) { db => ExistsFunctions.byFieldsNoMatch(db) }
@Test
@DisplayName("byContains fails")
def byContainsFails(): Unit =
Using(SQLiteDB()) { db => assertThrows(classOf[DocumentException], () => ExistsFunctions.byContainsMatch(db)) }
@Test
@DisplayName("byJsonPath fails")
def byJsonPathFails(): Unit =
Using(SQLiteDB()) { db => assertThrows(classOf[DocumentException], () => ExistsFunctions.byJsonPathMatch(db)) }

View File

@@ -0,0 +1,127 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.{DisplayName, Test}
import solutions.bitbadger.documents.DocumentException
import scala.util.Using
/**
* SQLite integration tests for the `Find` object / `find*` connection extension functions
*/
@DisplayName("Scala | SQLite: Find")
class SQLiteFindIT:
@Test
@DisplayName("all retrieves all documents")
def allDefault(): Unit =
Using(SQLiteDB()) { db => FindFunctions.allDefault(db) }
@Test
@DisplayName("all sorts data ascending")
def allAscending(): Unit =
Using(SQLiteDB()) { db => FindFunctions.allAscending(db) }
@Test
@DisplayName("all sorts data descending")
def allDescending(): Unit =
Using(SQLiteDB()) { db => FindFunctions.allDescending(db) }
@Test
@DisplayName("all sorts data numerically")
def allNumOrder(): Unit =
Using(SQLiteDB()) { db => FindFunctions.allNumOrder(db) }
@Test
@DisplayName("all succeeds with an empty table")
def allEmpty(): Unit =
Using(SQLiteDB()) { db => FindFunctions.allEmpty(db) }
@Test
@DisplayName("byId retrieves a document via a string ID")
def byIdString(): Unit =
Using(SQLiteDB()) { db => FindFunctions.byIdString(db) }
@Test
@DisplayName("byId retrieves a document via a numeric ID")
def byIdNumber(): Unit =
Using(SQLiteDB()) { db => FindFunctions.byIdNumber(db) }
@Test
@DisplayName("byId returns null when a matching ID is not found")
def byIdNotFound(): Unit =
Using(SQLiteDB()) { db => FindFunctions.byIdNotFound(db) }
@Test
@DisplayName("byFields retrieves matching documents")
def byFieldsMatch(): Unit =
Using(SQLiteDB()) { db => FindFunctions.byFieldsMatch(db) }
@Test
@DisplayName("byFields retrieves ordered matching documents")
def byFieldsMatchOrdered(): Unit =
Using(SQLiteDB()) { db => FindFunctions.byFieldsMatchOrdered(db) }
@Test
@DisplayName("byFields retrieves matching documents with a numeric IN clause")
def byFieldsMatchNumIn(): Unit =
Using(SQLiteDB()) { db => FindFunctions.byFieldsMatchNumIn(db) }
@Test
@DisplayName("byFields succeeds when no documents match")
def byFieldsNoMatch(): Unit =
Using(SQLiteDB()) { db => FindFunctions.byFieldsNoMatch(db) }
@Test
@DisplayName("byFields retrieves matching documents with an IN_ARRAY comparison")
def byFieldsMatchInArray(): Unit =
Using(SQLiteDB()) { db => FindFunctions.byFieldsMatchInArray(db) }
@Test
@DisplayName("byFields succeeds when no documents match an IN_ARRAY comparison")
def byFieldsNoMatchInArray(): Unit =
Using(SQLiteDB()) { db => FindFunctions.byFieldsNoMatchInArray(db) }
@Test
@DisplayName("byContains fails")
def byContainsFails(): Unit =
Using(SQLiteDB()) { db => assertThrows(classOf[DocumentException], () => FindFunctions.byContainsMatch(db)) }
@Test
@DisplayName("byJsonPath fails")
def byJsonPathFails(): Unit =
Using(SQLiteDB()) { db => assertThrows(classOf[DocumentException], () => FindFunctions.byJsonPathMatch(db)) }
@Test
@DisplayName("firstByFields retrieves a matching document")
def firstByFieldsMatchOne(): Unit =
Using(SQLiteDB()) { db => FindFunctions.firstByFieldsMatchOne(db) }
@Test
@DisplayName("firstByFields retrieves a matching document among many")
def firstByFieldsMatchMany(): Unit =
Using(SQLiteDB()) { db => FindFunctions.firstByFieldsMatchMany(db) }
@Test
@DisplayName("firstByFields retrieves a matching document among many (ordered)")
def firstByFieldsMatchOrdered(): Unit =
Using(SQLiteDB()) { db => FindFunctions.firstByFieldsMatchOrdered(db) }
@Test
@DisplayName("firstByFields returns null when no document matches")
def firstByFieldsNoMatch(): Unit =
Using(SQLiteDB()) { db => FindFunctions.firstByFieldsNoMatch(db) }
@Test
@DisplayName("firstByContains fails")
def firstByContainsFails(): Unit =
Using(SQLiteDB()) { db =>
assertThrows(classOf[DocumentException], () => FindFunctions.firstByContainsMatchOne(db))
}
@Test
@DisplayName("firstByJsonPath fails")
def firstByJsonPathFails(): Unit =
Using(SQLiteDB()) { db =>
assertThrows(classOf[DocumentException], () => FindFunctions.firstByJsonPathMatchOne(db))
}

View File

@@ -0,0 +1,44 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.{DisplayName, Test}
import org.junit.jupiter.api.Assertions.assertThrows
import solutions.bitbadger.documents.DocumentException
import scala.util.Using
/**
* SQLite integration tests for the `Patch` object / `patchBy*` connection extension functions
*/
@DisplayName("Scala | SQLite: Patch")
class SQLitePatchIT:
@Test
@DisplayName("byId patches an existing document")
def byIdMatch(): Unit =
Using(SQLiteDB()) { db => PatchFunctions.byIdMatch(db) }
@Test
@DisplayName("byId succeeds for a non-existent document")
def byIdNoMatch(): Unit =
Using(SQLiteDB()) { db => PatchFunctions.byIdNoMatch(db) }
@Test
@DisplayName("byFields patches matching document")
def byFieldsMatch(): Unit =
Using(SQLiteDB()) { db => PatchFunctions.byFieldsMatch(db) }
@Test
@DisplayName("byFields succeeds when no documents match")
def byFieldsNoMatch(): Unit =
Using(SQLiteDB()) { db => PatchFunctions.byFieldsNoMatch(db) }
@Test
@DisplayName("byContains fails")
def byContainsFails(): Unit =
Using(SQLiteDB()) { db => assertThrows(classOf[DocumentException], () => PatchFunctions.byContainsMatch(db)) }
@Test
@DisplayName("byJsonPath fails")
def byJsonPathFails(): Unit =
Using(SQLiteDB()) { db => assertThrows(classOf[DocumentException], () => PatchFunctions.byJsonPathMatch(db)) }

View File

@@ -0,0 +1,57 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.{DisplayName, Test}
import org.junit.jupiter.api.Assertions.assertThrows
import solutions.bitbadger.documents.DocumentException
import scala.util.Using
/**
* SQLite integration tests for the `RemoveFields` object / `removeFieldsBy*` connection extension functions
*/
@DisplayName("Scala | SQLite: RemoveFields")
class SQLiteRemoveFieldsIT:
@Test
@DisplayName("byId removes fields from an existing document")
def byIdMatchFields(): Unit =
Using(SQLiteDB()) { db => RemoveFieldsFunctions.byIdMatchFields(db) }
@Test
@DisplayName("byId succeeds when fields do not exist on an existing document")
def byIdMatchNoFields(): Unit =
Using(SQLiteDB()) { db => RemoveFieldsFunctions.byIdMatchNoFields(db) }
@Test
@DisplayName("byId succeeds when no document exists")
def byIdNoMatch(): Unit =
Using(SQLiteDB()) { db => RemoveFieldsFunctions.byIdNoMatch(db) }
@Test
@DisplayName("byFields removes fields from matching documents")
def byFieldsMatchFields(): Unit =
Using(SQLiteDB()) { db => RemoveFieldsFunctions.byFieldsMatchFields(db) }
@Test
@DisplayName("byFields succeeds when fields do not exist on matching documents")
def byFieldsMatchNoFields(): Unit =
Using(SQLiteDB()) { db => RemoveFieldsFunctions.byFieldsMatchNoFields(db) }
@Test
@DisplayName("byFields succeeds when no matching documents exist")
def byFieldsNoMatch(): Unit =
Using(SQLiteDB()) { db => RemoveFieldsFunctions.byFieldsNoMatch(db) }
@Test
@DisplayName("byContains fails")
def byContainsFails(): Unit =
Using(SQLiteDB()) { db =>
assertThrows(classOf[DocumentException], () => RemoveFieldsFunctions.byContainsMatchFields(db))
}
@Test
@DisplayName("byJsonPath fails")
def byJsonPathFails(): Unit =
Using(SQLiteDB()) { db =>
assertThrows(classOf[DocumentException], () => RemoveFieldsFunctions.byJsonPathMatchFields(db))
}

View File

@@ -0,0 +1,3 @@
package solutions.bitbadger.documents.scala.tests.integration
class SubDocument(val foo: String = "", val bar: String = "")

View File

@@ -0,0 +1,28 @@
package solutions.bitbadger.documents.scala.tests.integration
import solutions.bitbadger.documents.AutoId
import solutions.bitbadger.documents.java.DocumentConfig
import java.sql.Connection
/**
* Common trait for PostgreSQL and SQLite throwaway databases
*/
trait ThrowawayDatabase extends AutoCloseable:
/** The database connection for the throwaway database */
def conn: Connection
/**
* Determine if a database object exists
*
* @param name The name of the object whose existence should be checked
* @return True if the object exists, false if not
*/
def dbObjectExists(name: String): Boolean
/** The name for the throwaway database */
val dbName = s"throwaway_${AutoId.generateRandomString(8)}"
// Use a Jackson-based document serializer for testing
DocumentConfig.setSerializer(JacksonDocumentSerializer())

View File

@@ -0,0 +1,6 @@
package solutions.bitbadger.documents.scala
package object tests {
def TEST_TABLE = "test_table"
}