Initial Development (#1)

This project now contains:
- A generic JVM document library (with Kotlin extensions on the JDBC `Connection` object)
- A Groovy library which adds extension methods to the `Connection` object
- A Scala library, which uses native Scala collections and adds Scala-style extension methods to the `Connection` object
- A Kotlin library which uses `kotlinx.serialization` (no reflection, reified generic types) along with `Connection` extensions

Reviewed-on: #1
This commit was merged in pull request #1.
This commit is contained in:
2025-04-16 01:29:19 +00:00
parent 995f565255
commit d202c002b5
385 changed files with 41134 additions and 1 deletions

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

@@ -0,0 +1,167 @@
<?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>1.0.0-RC1</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://relationaldocs.bitbadger.solutions/jvm/</url>
<licenses>
<license>
<name>MIT License</name>
<url>https://www.opensource.org/licenses/mit-license.php</url>
</license>
</licenses>
<developers>
<developer>
<name>Daniel J. Summers</name>
<email>daniel@bitbadger.solutions</email>
<organization>Bit Badger Solutions</organization>
<organizationUrl>https://bitbadger.solutions</organizationUrl>
</developer>
</developers>
<scm>
<connection>scm:git:https://git.bitbadger.solutions/bit-badger/solutions.bitbadger.documents.git</connection>
<developerConnection>scm:git:https://git.bitbadger.solutions/bit-badger/solutions.bitbadger.documents.git</developerConnection>
<url>https://git.bitbadger.solutions/bit-badger/solutions.bitbadger.documents</url>
</scm>
<build>
<sourceDirectory>${project.basedir}/src/main/scala</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/scala</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${sourcePlugin.version}</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin>
<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>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>doc-jar</goal>
</goals>
<configuration>
<args>-nobootcp</args>
<scaladocClassName>dotty.tools.scaladoc.Main</scaladocClassName>
<sourceDir>${project.build.outputDirectory}</sourceDir>
<includes>
<include>**/*.tasty</include>
</includes>
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scaladoc_3</artifactId>
<version>${scala.version}</version>
</dependency>
</dependencies>
</configuration>
</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>
<!-- <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${sourcePlugin.version}</version>
<executions>
<execution>
<id>attach-sources</id>
<goals>
<goal>jar-no-fork</goal>
</goals>
</execution>
</executions>
</plugin> -->
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.7.0</version>
<extensions>true</extensions>
<configuration>
<deploymentName>Deployment-scala-${project.version}</deploymentName>
<publishingServerId>central</publishingServerId>
</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[A](tableName: String, criteria: A, 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[A](tableName: String, criteria: A): 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,360 @@
package solutions.bitbadger.documents.scala
import solutions.bitbadger.documents.{Configuration, Parameter}
import java.io.PrintWriter
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[Doc](query: String, parameters: Seq[Parameter[?]], conn: Connection,
mapFunc: (ResultSet, ClassTag[Doc]) => Doc)(using tag: ClassTag[Doc]): List[Doc] =
Using(Parameters.apply(conn, query, parameters)) { stmt => Results.toCustomList[Doc](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[Doc](query: String, conn: Connection, mapFunc: (ResultSet, ClassTag[Doc]) => Doc)
(using tag: ClassTag[Doc]): List[Doc] =
list(query, Nil, 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[Doc](query: String, parameters: Seq[Parameter[?]], mapFunc: (ResultSet, ClassTag[Doc]) => Doc)
(using tag: ClassTag[Doc]): List[Doc] =
Using(Configuration.dbConn()) { conn => list[Doc](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[Doc](query: String, mapFunc: (ResultSet, ClassTag[Doc]) => Doc)(using tag: ClassTag[Doc]): List[Doc] =
list(query, List(), mapFunc)
/**
* Execute a query that returns a JSON array 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 to extract the JSON from the query
* @return A JSON array of results for the given query
* @throws DocumentException If parameters are invalid
*/
def jsonArray(query: String, parameters: Seq[Parameter[?]], conn: Connection, mapFunc: ResultSet => String): String =
Using(Parameters.apply(conn, query, parameters)) { stmt => Results.toJsonArray(stmt, mapFunc) }.get
/**
* Execute a query that returns a JSON array 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 to extract the JSON from the query
* @return A JSON array of results for the given query
* @throws DocumentException If parameters are invalid
*/
def jsonArray(query: String, conn: Connection, mapFunc: ResultSet => String): String =
jsonArray(query, Nil, conn, mapFunc)
/**
* Execute a query that returns a JSON array 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 to extract the JSON from the query
* @return A JSON array of results for the given query
* @throws DocumentException If parameters are invalid
*/
def jsonArray(query: String, parameters: Seq[Parameter[?]], mapFunc: ResultSet => String): String =
Using(Configuration.dbConn()) { conn => jsonArray(query, parameters, conn, mapFunc) }.get
/**
* Execute a query that returns a JSON array of results (creates connection)
*
* @param query The query to retrieve the results
* @param mapFunc The mapping function to extract the JSON from the query
* @return A JSON array of results for the given query
* @throws DocumentException If parameters are invalid
*/
def jsonArray(query: String, mapFunc: ResultSet => String): String =
jsonArray(query, Nil, mapFunc)
/**
* Execute a query that writes a JSON array of results to the given `PrintWriter`
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
* @param writer The writer to which the results should be written
* @param conn The connection over which the query should be executed
* @param mapFunc The mapping function to extract the JSON from the query
* @return A JSON array of results for the given query
* @throws DocumentException If parameters are invalid
*/
def writeJsonArray(query: String, parameters: Seq[Parameter[?]], writer: PrintWriter, conn: Connection,
mapFunc: ResultSet => String): Unit =
Using(Parameters.apply(conn, query, parameters)) { stmt => Results.writeJsonArray(writer, stmt, mapFunc) }
/**
* Execute a query that returns a JSON array of results
*
* @param query The query to retrieve the results
* @param writer The writer to which the results should be written
* @param conn The connection over which the query should be executed
* @param mapFunc The mapping function to extract the JSON from the query
* @return A JSON array of results for the given query
* @throws DocumentException If parameters are invalid
*/
def writeJsonArray(query: String, conn: Connection, writer: PrintWriter, mapFunc: ResultSet => String): Unit =
writeJsonArray(query, Nil, writer, conn, mapFunc)
/**
* Execute a query that returns a JSON array of results (creates connection)
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
* @param writer The writer to which the results should be written
* @param mapFunc The mapping function to extract the JSON from the query
* @return A JSON array of results for the given query
* @throws DocumentException If parameters are invalid
*/
def writeJsonArray(query: String, parameters: Seq[Parameter[?]], writer: PrintWriter,
mapFunc: ResultSet => String): Unit =
Using(Configuration.dbConn()) { conn => writeJsonArray(query, parameters, writer, conn, mapFunc) }
/**
* Execute a query that returns a JSON array of results (creates connection)
*
* @param query The query to retrieve the results
* @param writer The writer to which the results should be written
* @param mapFunc The mapping function to extract the JSON from the query
* @return A JSON array of results for the given query
* @throws DocumentException If parameters are invalid
*/
def writeJsonArray(query: String, writer: PrintWriter, mapFunc: ResultSet => String): Unit =
writeJsonArray(query, Nil, writer, 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[Doc](query: String, parameters: Seq[Parameter[?]], conn: Connection,
mapFunc: (ResultSet, ClassTag[Doc]) => Doc)(using tag: ClassTag[Doc]): Option[Doc] =
list[Doc](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[Doc](query: String, conn: Connection, mapFunc: (ResultSet, ClassTag[Doc]) => Doc)
(using tag: ClassTag[Doc]): Option[Doc] =
list[Doc](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[Doc](query: String, parameters: Seq[Parameter[?]], mapFunc: (ResultSet, ClassTag[Doc]) => Doc)
(using tag: ClassTag[Doc]): Option[Doc] =
Using(Configuration.dbConn()) { conn => single[Doc](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[Doc](query: String, mapFunc: (ResultSet, ClassTag[Doc]) => Doc)(using tag: ClassTag[Doc]): Option[Doc] =
single[Doc](query, List(), mapFunc)
/**
* Execute a query that returns JSON for one or no documents
*
* @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 The JSON for the document if found, an empty object (`{}`) if not
* @throws DocumentException If parameters are invalid
*/
def jsonSingle(query: String, parameters: Seq[Parameter[?]], conn: Connection, mapFunc: ResultSet => String): String =
val result = jsonArray("$query LIMIT 1", parameters, conn, mapFunc)
result match
case "[]" => "{}"
case _ => result.substring(1, result.length - 1)
/**
* Execute a query that returns JSON for one or no documents
*
* @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 The JSON for the document if found, an empty object (`{}`) if not
* @throws DocumentException If parameters are invalid
*/
def jsonSingle(query: String, conn: Connection, mapFunc: ResultSet => String): String =
jsonSingle(query, Nil, conn, mapFunc)
/**
* Execute a query that returns JSON for one or no documents (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 The JSON for the document if found, an empty object (`{}`) if not
* @throws DocumentException If parameters are invalid
*/
def jsonSingle(query: String, parameters: Seq[Parameter[?]], mapFunc: ResultSet => String): String =
Using(Configuration.dbConn()) { conn => jsonSingle(query, parameters, conn, mapFunc) }.get
/**
* Execute a query that returns JSON for one or no documents (creates connection)
*
* @param query The query to retrieve the results
* @param mapFunc The mapping function between the document and the domain item
* @return The JSON for the document if found, an empty object (`{}`) if not
* @throws DocumentException If parameters are invalid
*/
def jsonSingle(query: String, mapFunc: ResultSet => String): String =
jsonSingle(query, Nil, 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[A](query: String, parameters: Seq[Parameter[?]], conn: Connection, mapFunc: (ResultSet, ClassTag[A]) => A)
(using tag: ClassTag[A]): A =
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[A](query: String, conn: Connection, mapFunc: (ResultSet, ClassTag[A]) => A)(using tag: ClassTag[A]): A =
scalar[A](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[A](query: String, parameters: Seq[Parameter[?]], mapFunc: (ResultSet, ClassTag[A]) => A)
(using tag: ClassTag[A]): A =
Using(Configuration.dbConn()) { conn => scalar[A](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[A](query: String, mapFunc: (ResultSet, ClassTag[A]) => A)(using tag: ClassTag[A]): A =
scalar[A](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[Key](tableName: String, docId: Key, 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[Key](tableName: String, docId: Key): 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[A](tableName: String, criteria: A, 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[A](tableName: String, criteria: A): 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[Doc](tableName: String, document: Doc, 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[Doc](tableName: String, document: Doc): 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[Doc](tableName: String, document: Doc, 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[Doc](tableName: String, document: Doc): 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[Key, Doc](tableName: String, docId: Key, document: Doc, 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[Key, Doc](tableName: String, docId: Key, document: Doc): 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[Key](tableName: String, docId: Key, 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[Key](tableName: String, docId: Key): 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[A](tableName: String, criteria: A, 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[A](tableName: String, criteria: A): 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,369 @@
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[Doc](tableName: String, orderBy: Seq[Field[?]], conn: Connection)(using tag: ClassTag[Doc]): List[Doc] =
Custom.list[Doc](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[Doc](tableName: String, conn: Connection)(using tag: ClassTag[Doc]): List[Doc] =
all[Doc](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[Doc](tableName: String, orderBy: Seq[Field[?]] = Nil)(using tag: ClassTag[Doc]): List[Doc] =
Using(Configuration.dbConn()) { conn => all[Doc](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[Key, Doc](tableName: String, docId: Key, conn: Connection)(using tag: ClassTag[Doc]): Option[Doc] =
Custom.single[Doc](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[Key, Doc](tableName: String, docId: Key)(using tag: ClassTag[Doc]): Option[Doc] =
Using(Configuration.dbConn()) { conn => byId[Key, Doc](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[Doc](tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch], orderBy: Seq[Field[?]],
conn: Connection)(using tag: ClassTag[Doc]): List[Doc] =
val named = Parameters.nameFields(fields)
Custom.list[Doc](
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[Doc](tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch], conn: Connection)
(using tag: ClassTag[Doc]): List[Doc] =
byFields[Doc](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[Doc](tableName: String, fields: Seq[Field[?]], orderBy: Seq[Field[?]], conn: Connection)
(using tag: ClassTag[Doc]): List[Doc] =
byFields[Doc](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[Doc](tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch] = None,
orderBy: Seq[Field[?]] = Nil)(using tag: ClassTag[Doc]): List[Doc] =
Using(Configuration.dbConn()) { conn => byFields[Doc](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[Doc, A](tableName: String, criteria: A, orderBy: Seq[Field[?]], conn: Connection)
(using tag: ClassTag[Doc]): List[Doc] =
Custom.list[Doc](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[Doc, A](tableName: String, criteria: A, conn: Connection)(using tag: ClassTag[Doc]): List[Doc] =
byContains[Doc, A](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[Doc, A](tableName: String, criteria: A, orderBy: Seq[Field[?]] = Nil)
(using tag: ClassTag[Doc]): List[Doc] =
Using(Configuration.dbConn()) { conn => byContains[Doc, A](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[Doc](tableName: String, path: String, orderBy: Seq[Field[?]], conn: Connection)
(using tag: ClassTag[Doc]): List[Doc] =
Custom.list[Doc](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[Doc](tableName: String, path: String, conn: Connection)(using tag: ClassTag[Doc]): List[Doc] =
byJsonPath[Doc](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[Doc](tableName: String, path: String, orderBy: Seq[Field[?]] = Nil)
(using tag: ClassTag[Doc]): List[Doc] =
Using(Configuration.dbConn()) { conn => byJsonPath[Doc](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[Doc](tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch],
orderBy: Seq[Field[?]], conn: Connection)(using tag: ClassTag[Doc]): Option[Doc] =
val named = Parameters.nameFields(fields)
Custom.single[Doc](
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[Doc](tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch], conn: Connection)
(using tag: ClassTag[Doc]): Option[Doc] =
firstByFields[Doc](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[Doc](tableName: String, fields: Seq[Field[?]], orderBy: Seq[Field[?]], conn: Connection)
(using tag: ClassTag[Doc]): Option[Doc] =
firstByFields[Doc](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[Doc](tableName: String, fields: Seq[Field[?]], conn: Connection)
(using tag: ClassTag[Doc]): Option[Doc] =
firstByFields[Doc](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[Doc](tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch] = None,
orderBy: Seq[Field[?]] = Nil)(using tag: ClassTag[Doc]): Option[Doc] =
Using(Configuration.dbConn()) { conn => firstByFields[Doc](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[Doc, A](tableName: String, criteria: A, orderBy: Seq[Field[?]], conn: Connection)
(using tag: ClassTag[Doc]): Option[Doc] =
Custom.single[Doc](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[Doc, A](tableName: String, criteria: A, conn: Connection)(using tag: ClassTag[Doc]): Option[Doc] =
firstByContains[Doc, A](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[Doc, A](tableName: String, criteria: A, orderBy: Seq[Field[?]] = Nil)
(using tag: ClassTag[Doc]): Option[Doc] =
Using(Configuration.dbConn()) { conn => firstByContains[Doc, A](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[Doc](tableName: String, path: String, orderBy: Seq[Field[?]], conn: Connection)
(using tag: ClassTag[Doc]): Option[Doc] =
Custom.single[Doc](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[Doc](tableName: String, path: String, conn: Connection)(using tag: ClassTag[Doc]): Option[Doc] =
firstByJsonPath[Doc](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[Doc](tableName: String, path: String, orderBy: Seq[Field[?]] = Nil)
(using tag: ClassTag[Doc]): Option[Doc] =
Using(Configuration.dbConn()) { conn => firstByJsonPath[Doc](tableName, path, orderBy, conn) }.get

View File

@@ -0,0 +1,688 @@
package solutions.bitbadger.documents.scala
import solutions.bitbadger.documents.{Field, FieldMatch}
import solutions.bitbadger.documents.java.Json as CoreJson
import java.io.PrintWriter
import java.sql.Connection
import _root_.scala.jdk.CollectionConverters.*
object Json:
/**
* 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 JSON array of documents from the given table
* @throws DocumentException If query execution fails
*/
def all(tableName: String, orderBy: Seq[Field[?]], conn: Connection): String =
CoreJson.all(tableName, orderBy.asJava, conn)
/**
* Retrieve all documents in the given table
*
* @param tableName The table from which documents should be retrieved
* @param conn The connection over which documents should be retrieved
* @return A JSON array of documents from the given table
* @throws DocumentException If query execution fails
*/
def all(tableName: String, conn: Connection): String =
CoreJson.all(tableName, conn)
/**
* Retrieve all documents in the given table, ordering results by the optional given fields (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 JSON array of documents from the given table
* @throws DocumentException If no connection string has been set, or if query execution fails
*/
def all(tableName: String, orderBy: Seq[Field[?]] = Nil): String =
CoreJson.all(tableName, orderBy.asJava)
/**
* Write all documents in the given table to the given `PrintWriter`, ordering results by the optional given fields
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @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
* @throws DocumentException If query execution fails
*/
def writeAll(tableName: String, writer: PrintWriter, orderBy: Seq[Field[?]], conn: Connection): Unit =
CoreJson.writeAll(tableName, writer, orderBy.asJava, conn)
/**
* Write all documents in the given table to the given `PrintWriter`
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @param conn The connection over which documents should be retrieved
* @throws DocumentException If query execution fails
*/
def writeAll(tableName: String, writer: PrintWriter, conn: Connection): Unit =
CoreJson.writeAll(tableName, writer, conn)
/**
* Write all documents in the given table to the given `PrintWriter`, ordering results by the optional given fields
* (creates connection)
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @throws DocumentException If query execution fails
*/
def writeAll(tableName: String, writer: PrintWriter, orderBy: Seq[Field[?]]): Unit =
CoreJson.writeAll(tableName, writer, orderBy.asJava)
/**
* 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 A JSON document if found, an empty JSON object if not found
* @throws DocumentException If no dialect has been configured
*/
def byId[Key](tableName: String, docId: Key, conn: Connection): String =
CoreJson.byId(tableName, docId, conn)
/**
* 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 A JSON document if found, an empty JSON object if not found
* @throws DocumentException If no connection string has been set
*/
def byId[Key](tableName: String, docId: Key): String =
CoreJson.byId(tableName, docId)
/**
* Write a document to the given `PrintWriter` by its ID
*
* @param tableName The table from which the document should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @param docId The ID of the document to retrieve
* @param conn The connection over which documents should be retrieved
* @throws DocumentException If no dialect has been configured
*/
def writeById[Key](tableName: String, writer: PrintWriter, docId: Key, conn: Connection): Unit =
CoreJson.writeById(tableName, writer, docId, conn)
/**
* Write a document to the given `PrintWriter` by its ID (creates connection)
*
* @param tableName The table from which the document should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @param docId The ID of the document to retrieve
* @throws DocumentException If no dialect has been configured
*/
def writeById[Key](tableName: String, writer: PrintWriter, docId: Key): Unit =
CoreJson.writeById(tableName, writer, docId)
/**
* 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 JSON array of documents matching the field comparison
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def byFields(tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch], orderBy: Seq[Field[?]],
conn: Connection): String =
CoreJson.byFields(tableName, fields.asJava, howMatched.orNull, orderBy.asJava, conn)
/**
* 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 JSON array of documents matching the field comparison
* @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): String =
CoreJson.byFields(tableName, fields.asJava, howMatched.orNull, 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 JSON array of documents matching the field comparison
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def byFields(tableName: String, fields: Seq[Field[?]], orderBy: Seq[Field[?]], conn: Connection): String =
CoreJson.byFields(tableName, fields.asJava, null, orderBy.asJava, 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 JSON array of documents matching the field comparison
* @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,
orderBy: Seq[Field[?]] = Nil): String =
CoreJson.byFields(tableName, fields.asJava, howMatched.orNull, orderBy.asJava)
/**
* Write documents to the given `PrintWriter` using a field comparison, ordering results by the given fields
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @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
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def writeByFields(tableName: String, writer: PrintWriter, fields: Seq[Field[?]], howMatched: Option[FieldMatch],
orderBy: Seq[Field[?]], conn: Connection): Unit =
CoreJson.writeByFields(tableName, writer, fields.asJava, howMatched.orNull, orderBy.asJava, conn)
/**
* Write documents to the given `PrintWriter` using a field comparison
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @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
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def writeByFields(tableName: String, writer: PrintWriter, fields: Seq[Field[?]], howMatched: Option[FieldMatch],
conn: Connection): Unit =
CoreJson.writeByFields(tableName, writer, fields.asJava, howMatched.orNull, conn)
/**
* Write documents to the given `PrintWriter` using a field comparison, ordering results by the given fields
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @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
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def writeByFields(tableName: String, writer: PrintWriter, fields: Seq[Field[?]], orderBy: Seq[Field[?]],
conn: Connection): Unit =
CoreJson.writeByFields(tableName, writer, fields.asJava, null, orderBy.asJava, conn)
/**
* Write documents to the given `PrintWriter` using a field comparison, ordering results by the given fields (creates
* connection)
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @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)
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def writeByFields(tableName: String, writer: PrintWriter, fields: Seq[Field[?]],
howMatched: Option[FieldMatch] = None, orderBy: Seq[Field[?]] = Nil): Unit =
CoreJson.writeByFields(tableName, writer, fields.asJava, howMatched.orNull, orderBy.asJava)
/**
* 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 JSON array of documents matching the JSON containment query
* @throws DocumentException If called on a SQLite connection
*/
def byContains[A](tableName: String, criteria: A, orderBy: Seq[Field[?]], conn: Connection): String =
CoreJson.byContains(tableName, criteria, orderBy.asJava, conn)
/**
* 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 JSON array of documents matching the JSON containment query
* @throws DocumentException If called on a SQLite connection
*/
def byContains[A](tableName: String, criteria: A, conn: Connection): String =
CoreJson.byContains(tableName, criteria, 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 JSON array 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[A](tableName: String, criteria: A, orderBy: Seq[Field[?]] = Nil): String =
CoreJson.byContains(tableName, criteria, orderBy.asJava)
/**
* Write documents to the given `PrintWriter` using a JSON containment query, ordering results by the given fields
* (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @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
* @throws DocumentException If called on a SQLite connection
*/
def writeByContains[A](tableName: String, writer: PrintWriter, criteria: A, orderBy: Seq[Field[?]],
conn: Connection): Unit =
CoreJson.writeByContains(tableName, writer, criteria, orderBy.asJava, conn)
/**
* Write documents to the given `PrintWriter` using a JSON containment query (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @param criteria The object for which JSON containment should be checked
* @param conn The connection over which documents should be retrieved
* @throws DocumentException If called on a SQLite connection
*/
def writeByContains[A](tableName: String, writer: PrintWriter, criteria: A, conn: Connection): Unit =
CoreJson.writeByContains(tableName, writer, criteria, conn)
/**
* Write documents to the given `PrintWriter` 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 writer The `PrintWriter` to which the results should be written
* @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)
* @throws DocumentException If called on a SQLite connection
*/
def writeByContains[A](tableName: String, writer: PrintWriter, criteria: A, orderBy: Seq[Field[?]] = Nil): Unit =
CoreJson.writeByContains(tableName, writer, criteria, orderBy.asJava)
/**
* 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 JSON array of documents matching the JSON Path match query
* @throws DocumentException If called on a SQLite connection
*/
def byJsonPath(tableName: String, path: String, orderBy: Seq[Field[?]], conn: Connection): String =
CoreJson.byJsonPath(tableName, path, orderBy.asJava, conn)
/**
* 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 JSON array of documents matching the JSON Path match query
* @throws DocumentException If called on a SQLite connection
*/
def byJsonPath(tableName: String, path: String, conn: Connection): String =
CoreJson.byJsonPath(tableName, path, 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 JSON array 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(tableName: String, path: String, orderBy: Seq[Field[?]] = Nil): String =
CoreJson.byJsonPath(tableName, path, orderBy.asJava)
/**
* Write documents to the given `PrintWriter` 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 writer The `PrintWriter` to which the results should be written
* @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
* @throws DocumentException If called on a SQLite connection
*/
def writeByJsonPath(tableName: String, writer: PrintWriter, path: String, orderBy: Seq[Field[?]],
conn: Connection): Unit =
CoreJson.writeByJsonPath(tableName, writer, path, orderBy.asJava, conn)
/**
* Write documents to the given `PrintWriter` using a JSON Path match query (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @param path The JSON path comparison to match
* @param conn The connection over which documents should be retrieved
* @throws DocumentException If called on a SQLite connection
*/
def writeByJsonPath(tableName: String, writer: PrintWriter, path: String, conn: Connection): Unit =
CoreJson.writeByJsonPath(tableName, writer, path, conn)
/**
* Write documents to the given `PrintWriter` 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 writer The `PrintWriter` to which the results should be written
* @param path The JSON path comparison to match
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @throws DocumentException If called on a SQLite connection
*/
def writeByJsonPath(tableName: String, writer: PrintWriter, path: String, orderBy: Seq[Field[?]] = Nil): Unit =
CoreJson.writeByJsonPath(tableName, writer, path, orderBy.asJava)
/**
* 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 The first JSON document matching the field comparison if found, an empty JSON object otherwise
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def firstByFields(tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch], orderBy: Seq[Field[?]],
conn: Connection): String =
CoreJson.firstByFields(tableName, fields.asJava, howMatched.orNull, orderBy.asJava, 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 howMatched How the fields should be matched (optional, defaults to `FieldMatch.ALL`)
* @param conn The connection over which documents should be retrieved
* @return The first JSON document matching the field comparison if found, an empty JSON object otherwise
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def firstByFields(tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch],
conn: Connection): String =
CoreJson.firstByFields(tableName, fields.asJava, howMatched.orNull, 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 The first JSON document matching the field comparison if found, an empty JSON object otherwise
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def firstByFields(tableName: String, fields: Seq[Field[?]], orderBy: Seq[Field[?]], conn: Connection): String =
CoreJson.firstByFields(tableName, fields.asJava, null, orderBy.asJava, 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 The first JSON document matching the field comparison if found, an empty JSON object otherwise
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def firstByFields(tableName: String, fields: Seq[Field[?]], conn: Connection): String =
CoreJson.firstByFields(tableName, fields.asJava, null, 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 The first JSON document matching the field comparison if found, an empty JSON object otherwise
* @throws DocumentException If no connection string has been set, or if parameters are invalid
*/
def firstByFields(tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch] = None,
orderBy: Seq[Field[?]] = Nil): String =
CoreJson.firstByFields(tableName, fields.asJava, howMatched.orNull, orderBy.asJava)
/**
* Write the first document to the given `PrintWriter` using a field comparison and ordering fields
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @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
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def writeFirstByFields(tableName: String, writer: PrintWriter, fields: Seq[Field[?]], howMatched: Option[FieldMatch],
orderBy: Seq[Field[?]], conn: Connection): Unit =
CoreJson.writeFirstByFields(tableName, writer, fields.asJava, howMatched.orNull, orderBy.asJava, conn)
/**
* Write the first document to the given `PrintWriter` using a field comparison
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @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
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def writeFirstByFields(tableName: String, writer: PrintWriter, fields: Seq[Field[?]], howMatched: Option[FieldMatch],
conn: Connection): Unit =
CoreJson.writeFirstByFields(tableName, writer, fields.asJava, howMatched.orNull, conn)
/**
* Write the first document to the given `PrintWriter` using a field comparison and ordering fields
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @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
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def writeFirstByFields(tableName: String, writer: PrintWriter, fields: Seq[Field[?]], orderBy: Seq[Field[?]],
conn: Connection): Unit =
CoreJson.writeFirstByFields(tableName, writer, fields.asJava, null, orderBy.asJava, conn)
/**
* Write the first document to the given `PrintWriter` using a field comparison
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @param fields The fields which should be compared
* @param conn The connection over which documents should be retrieved
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def writeFirstByFields(tableName: String, writer: PrintWriter, fields: Seq[Field[?]], conn: Connection): Unit =
CoreJson.writeFirstByFields(tableName, writer, fields.asJava, null, conn)
/**
* Write the first document to the given `PrintWriter` using a field comparison and ordering fields (creates
* connection)
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @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)
* @throws DocumentException If no dialect has been configured, or if parameters are invalid
*/
def writeFirstByFields(tableName: String, writer: PrintWriter, fields: Seq[Field[?]],
howMatched: Option[FieldMatch] = None, orderBy: Seq[Field[?]] = Nil): Unit =
CoreJson.writeFirstByFields(tableName, writer, fields.asJava, howMatched.orNull, orderBy.asJava)
/**
* 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 The first JSON document matching the JSON containment query if found, an empty JSON object otherwise
* @throws DocumentException If called on a SQLite connection
*/
def firstByContains[A](tableName: String, criteria: A, orderBy: Seq[Field[?]], conn: Connection): String =
CoreJson.firstByContains(tableName, criteria, orderBy.asJava, conn)
/**
* 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 The first JSON document matching the JSON containment query if found, an empty JSON object otherwise
* @throws DocumentException If called on a SQLite connection
*/
def firstByContains[A](tableName: String, criteria: A, conn: Connection): String =
CoreJson.firstByContains(tableName, criteria, 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 The first JSON document matching the JSON containment query if found, an empty JSON object otherwise
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def firstByContains[A](tableName: String, criteria: A, orderBy: Seq[Field[?]] = Nil): String =
CoreJson.firstByContains(tableName, criteria, orderBy.asJava)
/**
* Write the first document to the given `PrintWriter` using a JSON containment query and ordering fields (PostgreSQL
* only)
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @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
* @throws DocumentException If called on a SQLite connection
*/
def writeFirstByContains[A](tableName: String, writer: PrintWriter, criteria: A, orderBy: Seq[Field[?]],
conn: Connection): Unit =
CoreJson.writeFirstByContains(tableName, writer, criteria, orderBy.asJava, conn)
/**
* Write the first document to the given `PrintWriter` using a JSON containment query (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @param criteria The object for which JSON containment should be checked
* @param conn The connection over which documents should be retrieved
* @throws DocumentException If called on a SQLite connection
*/
def writeFirstByContains[A](tableName: String, writer: PrintWriter, criteria: A, conn: Connection): Unit =
CoreJson.writeFirstByContains(tableName, writer, criteria, conn)
/**
* Write the first document to the given `PrintWriter` using a JSON containment query and ordering fields (PostgreSQL
* only; creates connection)
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @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)
* @throws DocumentException If called on a SQLite connection
*/
def writeFirstByContains[A](tableName: String, writer: PrintWriter, criteria: A, orderBy: Seq[Field[?]] = Nil): Unit =
CoreJson.writeFirstByContains(tableName, writer, criteria, orderBy.asJava)
/**
* 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 The first JSON document matching the JSON Path match query if found, an empty JSON object otherwise
* @throws DocumentException If called on a SQLite connection
*/
def firstByJsonPath(tableName: String, path: String, orderBy: Seq[Field[?]], conn: Connection): String =
CoreJson.firstByJsonPath(tableName, path, orderBy.asJava, conn)
/**
* 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 The first JSON document matching the JSON Path match query if found, an empty JSON object otherwise
* @throws DocumentException If called on a SQLite connection
*/
def firstByJsonPath(tableName: String, path: String, conn: Connection): String =
CoreJson.firstByJsonPath(tableName, path, 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 The first JSON document matching the JSON Path match query if found, an empty JSON object otherwise
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def firstByJsonPath(tableName: String, path: String, orderBy: Seq[Field[?]] = Nil): String =
CoreJson.firstByJsonPath(tableName, path, orderBy.asJava)
/**
* Write the first document to the given `PrintWriter` using a JSON Path match query and ordering fields (PostgreSQL
* only)
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @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
* @throws DocumentException If called on a SQLite connection
*/
def writeFirstByJsonPath(tableName: String, writer: PrintWriter, path: String, orderBy: Seq[Field[?]],
conn: Connection): Unit =
CoreJson.writeFirstByJsonPath(tableName, writer, path, orderBy.asJava, conn)
/**
* Write the first document to the given `PrintWriter` using a JSON Path match query (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @param path The JSON path comparison to match
* @param conn The connection over which documents should be retrieved
* @throws DocumentException If called on a SQLite connection
*/
def writeFirstByJsonPath(tableName: String, writer: PrintWriter, path: String, conn: Connection): Unit =
CoreJson.writeFirstByJsonPath(tableName, writer, path, conn)
/**
* Write the first document to the given `PrintWriter` using a JSON Path match query and ordering fields (PostgreSQL
* only; creates connection)
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @param path The JSON path comparison to match
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @throws DocumentException If called on a SQLite connection
*/
def writeFirstByJsonPath(tableName: String, writer: PrintWriter, path: String, orderBy: Seq[Field[?]] = Nil): Unit =
CoreJson.writeFirstByJsonPath(tableName, writer, path, orderBy.asJava)

View File

@@ -0,0 +1,86 @@
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) then
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[A](name: String, value: A): 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[Key, Patch](tableName: String, docId: Key, patch: Patch, 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[Key, Patch](tableName: String, docId: Key, patch: Patch): Unit =
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[Patch](tableName: String, fields: Seq[Field[?]], patch: Patch, 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[Patch](tableName: String, fields: Seq[Field[?]], patch: Patch, 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[Patch](tableName: String, fields: Seq[Field[?]], patch: Patch,
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[A, Patch](tableName: String, criteria: A, patch: Patch, 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[A, Patch](tableName: String, criteria: A, patch: Patch): 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[Patch](tableName: String, path: String, patch: Patch, 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[Patch](tableName: String, path: String, patch: Patch): 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[Key](tableName: String, docId: Key, 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[Key](tableName: String, docId: Key, 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[A](tableName: String, criteria: A, 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[A](tableName: String, criteria: A, 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,143 @@
package solutions.bitbadger.documents.scala
import solutions.bitbadger.documents.DocumentException
import solutions.bitbadger.documents.java.Results as CoreResults
import java.io.PrintWriter
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[Doc](field: String, rs: ResultSet, ignored: ClassTag[Doc])(using tag: ClassTag[Doc]): Doc =
CoreResults.fromDocument(field, rs, tag.runtimeClass.asInstanceOf[Class[Doc]])
/**
* 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[Doc](rs: ResultSet, ignored: ClassTag[Doc])(using tag: ClassTag[Doc]): Doc =
fromDocument[Doc]("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[Doc](stmt: PreparedStatement,mapFunc: (ResultSet, ClassTag[Doc]) => Doc)
(using tag: ClassTag[Doc]): List[Doc] =
try
val buffer = ListBuffer[Doc]()
Using(stmt.executeQuery()) { rs =>
while rs.next() do
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)
/**
* Retrieve the JSON text of 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
* @return The JSON text of the document
*/
def jsonFromDocument(field: String, rs: ResultSet): String =
CoreResults.jsonFromDocument(field, rs)
/**
* Retrieve the JSON text of a document, specifying the field in which the document is found
*
* @param rs A `ResultSet` set to the row with the document to be constructed
* @return The JSON text of the document
*/
def jsonFromData(rs: ResultSet): String =
CoreResults.jsonFromData(rs)
/**
* Create a JSON array 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 JSON text
* @return A string with a JSON array of documents from the query's result
* @throws DocumentException If there is a problem executing the query (unchecked)
*/
def toJsonArray(stmt: PreparedStatement, mapFunc: ResultSet => String): String =
try
val results = StringBuilder("[")
Using(stmt.executeQuery()) { rs =>
while rs.next() do
if (results.length > 2) results.append(",")
results.append(mapFunc(rs))
}
results.append("]").toString()
catch
case ex: SQLException => throw DocumentException("Error retrieving documents from query: ${ex.message}", ex)
/**
* Write a JSON array of items for the results of the given command to the given `PrintWriter`, using the specified
* mapping function
*
* @param writer The writer for the results of the query
* @param stmt The prepared statement to execute
* @param mapFunc The mapping function from data reader to JSON text
* @return A string with a JSON array of documents from the query's result
* @throws DocumentException If there is a problem executing the query (unchecked)
*/
def writeJsonArray(writer: PrintWriter, stmt: PreparedStatement, mapFunc: ResultSet => String): Unit =
try
writer.write("[")
Using(stmt.executeQuery()) { rs =>
var isFirst = true
while rs.next() do
if isFirst then
isFirst = false
else
writer.write(",")
writer.write(mapFunc(rs))
}
writer.write("]")
catch
case ex: SQLException => throw DocumentException("Error writing documents from query: ${ex.message}", ex)

View File

@@ -0,0 +1,776 @@
package solutions.bitbadger.documents.scala.extensions
import solutions.bitbadger.documents.{DocumentIndex, Field, FieldMatch, Parameter}
import solutions.bitbadger.documents.scala.*
import java.io.PrintWriter
import java.sql.{Connection, ResultSet}
import scala.reflect.ClassTag
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[Doc](query: String, parameters: Seq[Parameter[?]], mapFunc: (ResultSet, ClassTag[Doc]) => Doc)
(using tag: ClassTag[Doc]): List[Doc] =
Custom.list[Doc](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[Doc](query: String, mapFunc: (ResultSet, ClassTag[Doc]) => Doc)
(using tag: ClassTag[Doc]): List[Doc] =
Custom.list[Doc](query, conn, mapFunc)
/**
* Execute a query that returns a JSON array of results
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
* @param mapFunc The mapping function to extract the JSON from the query
* @return A JSON array of results for the given query
* @throws DocumentException If parameters are invalid
*/
def customJsonArray(query: String, parameters: Seq[Parameter[?]], mapFunc: ResultSet => String): String =
Custom.jsonArray(query, parameters, conn, mapFunc)
/**
* Execute a query that returns a JSON array of results
*
* @param query The query to retrieve the results
* @param mapFunc The mapping function to extract the JSON from the query
* @return A JSON array of results for the given query
* @throws DocumentException If parameters are invalid
*/
def customJsonArray(query: String, mapFunc: ResultSet => String): String =
Custom.jsonArray(query, mapFunc)
/**
* Execute a query that writes a JSON array of results to the given `PrintWriter`
*
* @param query The query to retrieve the results
* @param parameters Parameters to use for the query
* @param writer The writer to which the results should be written
* @param mapFunc The mapping function to extract the JSON from the query
* @return A JSON array of results for the given query
* @throws DocumentException If parameters are invalid
*/
def writeCustomJsonArray(query: String, parameters: Seq[Parameter[?]], writer: PrintWriter,
mapFunc: ResultSet => String): Unit =
Custom.writeJsonArray(query, parameters, writer, conn, mapFunc)
/**
* Execute a query that writes a JSON array of results to the given `PrintWriter`
*
* @param query The query to retrieve the results
* @param writer The writer to which the results should be written
* @param mapFunc The mapping function to extract the JSON from the query
* @return A JSON array of results for the given query
* @throws DocumentException If parameters are invalid
*/
def writeCustomJsonArray(query: String, writer: PrintWriter, mapFunc: ResultSet => String): Unit =
Custom.writeJsonArray(query, writer, 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[Doc](query: String, parameters: Seq[Parameter[?]], mapFunc: (ResultSet, ClassTag[Doc]) => Doc)
(using tag: ClassTag[Doc]): Option[Doc] =
Custom.single[Doc](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[Doc](query: String, mapFunc: (ResultSet, ClassTag[Doc]) => Doc)
(using tag: ClassTag[Doc]): Option[Doc] =
Custom.single[Doc](query, conn, mapFunc)
/**
* Execute a query that returns JSON for one or no documents (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 The JSON for the document if found, an empty object (`{}`) if not
* @throws DocumentException If parameters are invalid
*/
def customJsonSingle(query: String, parameters: Seq[Parameter[?]], mapFunc: ResultSet => String): String =
Custom.jsonSingle(query, parameters, conn, mapFunc)
/**
* Execute a query that returns JSON for one or no documents (creates connection)
*
* @param query The query to retrieve the results
* @param mapFunc The mapping function between the document and the domain item
* @return The JSON for the document if found, an empty object (`{}`) if not
* @throws DocumentException If parameters are invalid
*/
def customJsonSingle(query: String, mapFunc: ResultSet => String): String =
Custom.jsonSingle(query, 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[?]] = Nil): 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[A](query: String, parameters: Seq[Parameter[?]], mapFunc: (ResultSet, ClassTag[A]) => A)
(using tag: ClassTag[A]): A =
Custom.scalar[A](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[A](query: String, mapFunc: (ResultSet, ClassTag[A]) => A)(using tag: ClassTag[A]): A =
Custom.scalar[A](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[Doc](tableName: String, document: Doc): 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[Doc](tableName: String, document: Doc): 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[Key, Doc](tableName: String, docId: Key, document: Doc): 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[A](tableName: String, criteria: A): 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[Key](tableName: String, docId: Key): 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[A](tableName: String, criteria: A): 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 (Domain Objects) ~~~
/**
* 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[Doc](tableName: String, orderBy: Seq[Field[?]] = Nil)(using tag: ClassTag[Doc]): List[Doc] =
Find.all[Doc](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[Key, Doc](tableName: String, docId: Key)(using tag: ClassTag[Doc]): Option[Doc] =
Find.byId[Key, Doc](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[Doc](tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch] = None,
orderBy: Seq[Field[?]] = Nil)(using tag: ClassTag[Doc]): List[Doc] =
Find.byFields[Doc](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[Doc, A](tableName: String, criteria: A, orderBy: Seq[Field[?]] = Nil)
(using tag: ClassTag[Doc]): List[Doc] =
Find.byContains[Doc, A](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[Doc](tableName: String, path: String, orderBy: Seq[Field[?]] = Nil)
(using tag: ClassTag[Doc]): List[Doc] =
Find.byJsonPath[Doc](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[Doc](tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch] = None,
orderBy: Seq[Field[?]] = Nil)(using tag: ClassTag[Doc]): Option[Doc] =
Find.firstByFields[Doc](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[Doc, A](tableName: String, criteria: A, orderBy: Seq[Field[?]] = Nil)
(using tag: ClassTag[Doc]): Option[Doc] =
Find.firstByContains[Doc, A](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[Doc](tableName: String, path: String, orderBy: Seq[Field[?]] = Nil)
(using tag: ClassTag[Doc]): Option[Doc] =
Find.firstByJsonPath[Doc](tableName, path, orderBy, conn)
// ~~~ DOCUMENT RETRIEVAL QUERIES (Raw JSON) ~~~
/**
* Retrieve all documents in the given table
*
* @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 JSON array of documents from the given table
* @throws DocumentException If no connection string has been set, or if query execution fails
*/
def jsonAll(tableName: String, orderBy: Seq[Field[?]] = Nil): String =
Json.all(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 A JSON document if found, an empty JSON object if not found
* @throws DocumentException If no connection string has been set
*/
def jsonById[Key](tableName: String, docId: Key): String =
Json.byId(tableName, docId, 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 howMatched How the fields should be matched
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @return A JSON array of documents matching the field comparison
* @throws DocumentException If no connection string has been set, or if parameters are invalid
*/
def jsonByFields(tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch] = None,
orderBy: Seq[Field[?]] = Nil): String =
Json.byFields(tableName, fields, howMatched, orderBy, conn)
/**
* 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)
* @return A JSON array of documents matching the JSON containment query
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def jsonByContains[A](tableName: String, criteria: A, orderBy: Seq[Field[?]] = Nil): String =
Json.byContains(tableName, criteria, orderBy, conn)
/**
* 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)
* @return A JSON array 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 jsonByJsonPath(tableName: String, path: String, orderBy: Seq[Field[?]] = Nil): String =
Json.byJsonPath(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 JSON document matching the field comparison if found, an empty JSON object otherwise
* @throws DocumentException If no connection string has been set, or if parameters are invalid
*/
def jsonFirstByFields(tableName: String, fields: Seq[Field[?]], howMatched: Option[FieldMatch] = None,
orderBy: Seq[Field[?]] = Nil): String =
Json.firstByFields(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 JSON document matching the JSON containment query if found, an empty JSON object otherwise
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def jsonFirstByContains[A](tableName: String, criteria: A, orderBy: Seq[Field[?]] = Nil): String =
Json.firstByContains(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 JSON document matching the JSON Path match query if found, an empty JSON object otherwise
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def jsonFirstByJsonPath(tableName: String, path: String, orderBy: Seq[Field[?]] = Nil): String =
Json.firstByJsonPath(tableName, path, orderBy, conn)
// ~~~ DOCUMENT RETRIEVAL QUERIES (Write raw JSON to PrintWriter) ~~~
/**
* Write all documents in the given table to the given `PrintWriter`, ordering results by the optional given fields
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @throws DocumentException If no connection string has been set, or if query execution fails
*/
def writeJsonAll(tableName: String, writer: PrintWriter, orderBy: Seq[Field[?]] = Nil): Unit =
Json.writeAll(tableName, writer, orderBy, conn)
/**
* Write a document to the given `PrintWriter` by its ID
*
* @param tableName The table from which the document should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @param docId The ID of the document to retrieve
* @throws DocumentException If no connection string has been set
*/
def writeJsonById[Key](tableName: String, writer: PrintWriter, docId: Key): Unit =
Json.writeById(tableName, writer, docId, conn)
/**
* Write documents to the given `PrintWriter` using a field comparison, ordering results by the given fields
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @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)
* @throws DocumentException If no connection string has been set, or if parameters are invalid
*/
def writeJsonByFields(tableName: String, writer: PrintWriter, fields: Seq[Field[?]],
howMatched: Option[FieldMatch] = None, orderBy: Seq[Field[?]] = Nil): Unit =
Json.writeByFields(tableName, writer, fields, howMatched, orderBy, conn)
/**
* Write documents to the given `PrintWriter` using a JSON containment query, ordering results by the given fields
* (PostgreSQL only)
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @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)
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def writeJsonByContains[A](tableName: String, writer: PrintWriter, criteria: A, orderBy: Seq[Field[?]] = Nil): Unit =
Json.writeByContains(tableName, writer, criteria, orderBy, conn)
/**
* Write documents to the given `PrintWriter` 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 writer The `PrintWriter` to which the results should be written
* @param path The JSON path comparison to match
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def writeJsonByJsonPath(tableName: String, writer: PrintWriter, path: String, orderBy: Seq[Field[?]] = Nil): Unit =
Json.writeByJsonPath(tableName, writer, path, orderBy, conn)
/**
* Write the first document to the given `PrintWriter` using a field comparison and ordering fields
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @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)
* @throws DocumentException If no connection string has been set, or if parameters are invalid
*/
def writeJsonFirstByFields(tableName: String, writer: PrintWriter, fields: Seq[Field[?]],
howMatched: Option[FieldMatch] = None, orderBy: Seq[Field[?]] = Nil): Unit =
Json.writeFirstByFields(tableName, writer, fields, howMatched, orderBy, conn)
/**
* Write the first document to the given `PrintWriter` using a JSON containment query and ordering fields (PostgreSQL
* only)
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @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)
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def writeJsonFirstByContains[A](tableName: String, writer: PrintWriter, criteria: A,
orderBy: Seq[Field[?]] = Nil): Unit =
Json.writeFirstByContains(tableName, writer, criteria, orderBy, conn)
/**
* Write the first document to the given `PrintWriter` using a JSON Path match query and ordering fields (PostgreSQL
* only)
*
* @param tableName The table from which documents should be retrieved
* @param writer The `PrintWriter` to which the results should be written
* @param path The JSON path comparison to match
* @param orderBy Fields by which the query should be ordered (optional, defaults to no ordering)
* @throws DocumentException If no connection string has been set, or if called on a SQLite connection
*/
def writeJsonFirstByJsonPath(tableName: String, writer: PrintWriter, path: String,
orderBy: Seq[Field[?]] = Nil): Unit =
Json.writeFirstByJsonPath(tableName, writer, 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[Key, Patch](tableName: String, docId: Key, patch: Patch): 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[Patch](tableName: String, fields: Seq[Field[?]], patch: Patch,
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[A, Patch](tableName: String, criteria: A, patch: Patch): 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[Patch](tableName: String, path: String, patch: Patch): 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[Key](tableName: String, docId: Key, 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[A](tableName: String, criteria: A, 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[Key](tableName: String, docId: Key): 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[A](tableName: String, criteria: A): 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,33 @@
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,32 @@
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,109 @@
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,109 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.Assertions.*
import solutions.bitbadger.documents.query.{CountQuery, DeleteQuery, FindQuery, QueryUtils}
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}
import java.io.{PrintWriter, StringWriter}
import scala.jdk.CollectionConverters.*
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 jsonArrayEmpty(db: ThrowawayDatabase): Unit =
assertEquals(0L, db.conn.countAll(TEST_TABLE), "The test table should be empty")
assertEquals("[]", db.conn.customJsonArray(FindQuery.all(TEST_TABLE), Nil, Results.jsonFromData),
"An empty list was not represented correctly")
def jsonArraySingle(db: ThrowawayDatabase): Unit =
db.conn.insert(TEST_TABLE, ArrayDocument("one", "2" :: "3" :: Nil))
assertEquals(JsonFunctions.maybeJsonB("""[{"id":"one","values":["2","3"]}]"""),
db.conn.customJsonArray(FindQuery.all(TEST_TABLE), Nil, Results.jsonFromData),
"A single document list was not represented correctly")
def jsonArrayMany(db: ThrowawayDatabase): Unit =
ArrayDocument.testDocuments.foreach { doc => db.conn.insert(TEST_TABLE, doc) }
assertEquals(JsonFunctions.maybeJsonB("""[{"id":"first","values":["a","b","c"]},"""
+ """{"id":"second","values":["c","d","e"]},{"id":"third","values":["x","y","z"]}]"""),
db.conn.customJsonArray(FindQuery.all(TEST_TABLE) + QueryUtils.orderBy((Field.named("id") :: Nil).asJava), Nil,
Results.jsonFromData),
"A multiple document list was not represented correctly")
def writeJsonArrayEmpty(db: ThrowawayDatabase): Unit =
assertEquals(0L, db.conn.countAll(TEST_TABLE), "The test table should be empty")
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeCustomJsonArray(FindQuery.all(TEST_TABLE), Nil, writer, Results.jsonFromData)
assertEquals("[]", output.toString, "An empty list was not represented correctly")
def writeJsonArraySingle(db: ThrowawayDatabase): Unit =
db.conn.insert(TEST_TABLE, ArrayDocument("one", "2" :: "3" :: Nil))
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeCustomJsonArray(FindQuery.all(TEST_TABLE), Nil, writer, Results.jsonFromData)
assertEquals(JsonFunctions.maybeJsonB("""[{"id":"one","values":["2","3"]}]"""), output.toString,
"A single document list was not represented correctly")
def writeJsonArrayMany(db: ThrowawayDatabase): Unit =
ArrayDocument.testDocuments.foreach { doc => db.conn.insert(TEST_TABLE, doc) }
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeCustomJsonArray(FindQuery.all(TEST_TABLE) + QueryUtils.orderBy((Field.named("id") :: Nil).asJava), Nil,
writer, Results.jsonFromData)
assertEquals(JsonFunctions.maybeJsonB("""[{"id":"first","values":["a","b","c"]},"""
+ """{"id":"second","values":["c","d","e"]},{"id":"third","values":["x","y","z"]}]"""),
output.toString, "A multiple document list was not represented correctly")
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 jsonSingleNone(db: ThrowawayDatabase): Unit =
assertEquals("{}", db.conn.customJsonSingle(FindQuery.all(TEST_TABLE), Nil, Results.jsonFromData),
"An empty document was not represented correctly")
def jsonSingleOne(db: ThrowawayDatabase): Unit =
db.conn.insert(TEST_TABLE, ArrayDocument("me", "myself" :: "i" :: Nil))
assertEquals(JsonFunctions.maybeJsonB("{\"id\":\"me\",\"values\":[\"myself\",\"i\"]}"),
db.conn.customJsonSingle(FindQuery.all(TEST_TABLE), Nil, Results.jsonFromData),
"A single document was not represented correctly")
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,108 @@
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,216 @@
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,34 @@
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) }
/** Document ID `one` as a JSON string */
val one = """{"id":"one","value":"FIRST!","numValue":0,"sub":null}"""
/** Document ID `two` as a JSON string */
val two = """{"id":"two","value":"another","numValue":10,"sub":{"foo":"green","bar":"blue"}}"""
/** Document ID `three` as a JSON string */
val three = """{"id":"three","value":"","numValue":4,"sub":null}"""
/** Document ID `four` as a JSON string */
val four = """{"id":"four","value":"purple","numValue":17,"sub":{"foo":"green","bar":"red"}}"""
/** Document ID `five` as a JSON string */
val five = """{"id":"five","value":"purple","numValue":18,"sub":null}"""

View File

@@ -0,0 +1,572 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.Assertions.*
import solutions.bitbadger.documents.{Configuration, Dialect, Field, FieldMatch}
import solutions.bitbadger.documents.scala.extensions.*
import solutions.bitbadger.documents.scala.tests.TEST_TABLE
import java.io.{PrintWriter, StringWriter}
import scala.jdk.CollectionConverters.*
/**
* Tests for the JSON-returning functions
*
* NOTE: PostgreSQL JSONB columns do not preserve the original JSON with which a document was stored. These tests are
* the most complex within the library, as they have split testing based on the backing data store. The PostgreSQL tests
* check IDs (and, in the case of ordered queries, which ones occur before which others) vs. the entire JSON string.
* Meanwhile, SQLite stores JSON as text, and will return exactly the JSON it was given when it was originally written.
* These tests can ensure the expected round-trip of the entire JSON string.
*/
object JsonFunctions:
/**
* PostgreSQL, when returning JSONB as a string, has spaces after commas and colons delineating fields and values.
* This function will do a crude string replacement to match the target string based on the dialect being tested.
*
* @param json The JSON which should be returned
* @return The actual expected JSON based on the database being tested
*/
def maybeJsonB(json: String): String =
Configuration.dialect() match
case Dialect.SQLITE => json
case Dialect.POSTGRESQL => json.replace("\":", "\": ").replace(",\"", ", \"")
/**
* Create a snippet of JSON to find a document ID
*
* @param id The ID of the document
* @return A connection-aware ID to check for presence and positioning
*/
private def docId(id: String): String =
maybeJsonB(s"""{"id":"$id"""")
private def checkAllDefault(json: String): Unit =
assertTrue(json.startsWith("["), s"JSON should start with '[' ($json)")
Configuration.dialect() match
case Dialect.SQLITE =>
assertTrue(json.contains(JsonDocument.one), s"Document 'one' not found in JSON ($json)")
assertTrue(json.contains(JsonDocument.two), s"Document 'two' not found in JSON ($json)")
assertTrue(json.contains(JsonDocument.three), s"Document 'three' not found in JSON ($json)")
assertTrue(json.contains(JsonDocument.four), s"Document 'four' not found in JSON ($json)")
assertTrue(json.contains(JsonDocument.five), s"Document 'five' not found in JSON ($json)")
case Dialect.POSTGRESQL =>
assertTrue(json.contains(docId("one")), s"Document 'one' not found in JSON ($json)")
assertTrue(json.contains(docId("two")), s"Document 'two' not found in JSON ($json)")
assertTrue(json.contains(docId("three")), s"Document 'three' not found in JSON ($json)")
assertTrue(json.contains(docId("four")), s"Document 'four' not found in JSON ($json)")
assertTrue(json.contains(docId("five")), s"Document 'five' not found in JSON ($json)")
assertTrue(json.endsWith("]"), s"JSON should end with ']' ($json)")
def allDefault(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkAllDefault(db.conn.jsonAll(TEST_TABLE))
def writeAllDefault(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonAll(TEST_TABLE, writer)
checkAllDefault(output.toString)
private def checkAllEmpty(json: String): Unit =
assertEquals("[]", json, "There should have been no documents returned")
def allEmpty(db: ThrowawayDatabase): Unit =
checkAllEmpty(db.conn.jsonAll(TEST_TABLE))
def writeAllEmpty(db: ThrowawayDatabase): Unit =
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonAll(TEST_TABLE, writer)
checkAllEmpty(output.toString)
private def checkByIdString(json: String): Unit =
Configuration.dialect() match
case Dialect.SQLITE => assertEquals(JsonDocument.two, json, "An incorrect document was returned")
case Dialect.POSTGRESQL => assertTrue(json.contains(docId("two")), s"An incorrect document was returned ($json)")
def byIdString(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkByIdString(db.conn.jsonById(TEST_TABLE, "two"))
def writeByIdString(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonById(TEST_TABLE, writer, "two")
checkByIdString(output.toString)
private def checkByIdNumber(json: String): Unit =
assertEquals(maybeJsonB("""{"key":18,"text":"howdy"}"""), json, "The document should have been found by numeric ID")
def byIdNumber(db: ThrowawayDatabase): Unit =
Configuration.idField = "key"
try
db.conn.insert(TEST_TABLE, NumIdDocument(18, "howdy"))
checkByIdNumber(db.conn.jsonById(TEST_TABLE, 18))
finally
Configuration.idField = "id"
def writeByIdNumber(db: ThrowawayDatabase): Unit =
Configuration.idField = "key"
try
db.conn.insert(TEST_TABLE, NumIdDocument(18, "howdy"))
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonById(TEST_TABLE, writer, 18)
checkByIdNumber(output.toString)
finally
Configuration.idField = "id"
private def checkByIdNotFound(json: String): Unit =
assertEquals("{}", json, "There should have been no document returned")
def byIdNotFound(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkByIdNotFound(db.conn.jsonById(TEST_TABLE, "x"))
def writeByIdNotFound(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonById(TEST_TABLE, writer, "x")
checkByIdNotFound(output.toString)
private def checkByFieldsMatch(json: String): Unit =
Configuration.dialect() match
case Dialect.SQLITE =>
assertEquals(s"[${JsonDocument.four}]", json, "The incorrect document was returned")
case Dialect.POSTGRESQL =>
assertTrue(json.startsWith("["), s"JSON should start with '[' ($json)")
assertTrue(json.contains(docId("four")), s"The incorrect document was returned ($json)")
assertTrue(json.endsWith("]"), s"JSON should end with ']' ($json)")
def byFieldsMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkByFieldsMatch(db.conn.jsonByFields(TEST_TABLE,
Field.any("value", ("blue" :: "purple" :: Nil).asJava) :: Field.exists("sub") :: Nil, Some(FieldMatch.ALL)))
def writeByFieldsMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonByFields(TEST_TABLE, writer,
Field.any("value", ("blue" :: "purple" :: Nil).asJava) :: Field.exists("sub") :: Nil, Some(FieldMatch.ALL))
checkByFieldsMatch(output.toString)
private def checkByFieldsMatchOrdered(json: String): Unit =
Configuration.dialect() match
case Dialect.SQLITE =>
assertEquals(s"[${JsonDocument.five},${JsonDocument.four}]", json, "The documents were not ordered correctly")
case Dialect.POSTGRESQL =>
val fiveIdx = json.indexOf(docId("five"))
val fourIdx = json.indexOf(docId("four"))
assertTrue(json.startsWith("["), s"JSON should start with '[' ($json)")
assertTrue(fiveIdx >= 0, s"Document 'five' not found ($json)")
assertTrue(fourIdx >= 0, s"Document 'four' not found ($json)")
assertTrue(fiveIdx < fourIdx, s"Document 'five' should have been before 'four' ($json)")
assertTrue(json.endsWith("]"), s"JSON should end with ']' ($json)")
def byFieldsMatchOrdered(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkByFieldsMatchOrdered(db.conn.jsonByFields(TEST_TABLE, Field.equal("value", "purple") :: Nil, None,
Field.named("id") :: Nil))
def writeByFieldsMatchOrdered(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonByFields(TEST_TABLE, writer, Field.equal("value", "purple") :: Nil, None, Field.named("id") :: Nil)
checkByFieldsMatchOrdered(output.toString)
private def checkByFieldsMatchNumIn(json: String): Unit =
Configuration.dialect() match
case Dialect.SQLITE =>
assertEquals(s"[${JsonDocument.three}]", json, "The incorrect document was returned")
case Dialect.POSTGRESQL =>
assertTrue(json.startsWith("["), s"JSON should start with '[' ($json)")
assertTrue(json.contains(docId("three")), s"The incorrect document was returned ($json)")
assertTrue(json.endsWith("]"), s"JSON should end with ']' ($json)")
def byFieldsMatchNumIn(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkByFieldsMatchNumIn(db.conn.jsonByFields(TEST_TABLE,
Field.any("numValue", (2 :: 4 :: 6 :: 8 :: Nil).asJava) :: Nil))
def writeByFieldsMatchNumIn(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonByFields(TEST_TABLE, writer, Field.any("numValue", (2 :: 4 :: 6 :: 8 :: Nil).asJava) :: Nil)
checkByFieldsMatchNumIn(output.toString)
private def checkByFieldsNoMatch(json: String): Unit =
assertEquals("[]", json, "There should have been no documents returned")
def byFieldsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkByFieldsNoMatch(db.conn.jsonByFields(TEST_TABLE, Field.greater("numValue", 100) :: Nil))
def writeByFieldsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonByFields(TEST_TABLE, writer, Field.greater("numValue", 100) :: Nil)
checkByFieldsNoMatch(output.toString)
private def checkByFieldsMatchInArray(json: String): Unit =
assertTrue(json.startsWith("["), s"JSON should start with '[' ($json)")
assertTrue(json.contains(docId("first")), s"The 'first' document was not found ($json)")
assertTrue(json.contains(docId("second")), s"The 'second' document was not found ($json)")
assertTrue(json.endsWith("]"), s"JSON should end with ']' ($json)")
def byFieldsMatchInArray(db: ThrowawayDatabase): Unit =
ArrayDocument.testDocuments.foreach { doc => db.conn.insert(TEST_TABLE, doc) }
checkByFieldsMatchInArray(db.conn.jsonByFields(TEST_TABLE,
Field.inArray("values", TEST_TABLE, ("c" :: Nil).asJava) :: Nil))
def writeByFieldsMatchInArray(db: ThrowawayDatabase): Unit =
ArrayDocument.testDocuments.foreach { doc => db.conn.insert(TEST_TABLE, doc) }
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonByFields(TEST_TABLE, writer, Field.inArray("values", TEST_TABLE, ("c" :: Nil).asJava) :: Nil)
checkByFieldsMatchInArray(output.toString)
private def checkByFieldsNoMatchInArray(json: String): Unit =
assertEquals("[]", json, "There should have been no documents returned")
def byFieldsNoMatchInArray(db: ThrowawayDatabase): Unit =
ArrayDocument.testDocuments.foreach { doc => db.conn.insert(TEST_TABLE, doc) }
checkByFieldsNoMatchInArray(db.conn.jsonByFields(TEST_TABLE,
Field.inArray("values", TEST_TABLE, ("j" :: Nil).asJava) :: Nil))
def writeByFieldsNoMatchInArray(db: ThrowawayDatabase): Unit =
ArrayDocument.testDocuments.foreach { doc => db.conn.insert(TEST_TABLE, doc) }
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonByFields(TEST_TABLE, writer, Field.inArray("values", TEST_TABLE, ("j" :: Nil).asJava) :: Nil)
checkByFieldsNoMatchInArray(output.toString)
private def checkByContainsMatch(json: String): Unit =
assertTrue(json.startsWith("["), s"JSON should start with '[' ($json)")
Configuration.dialect() match
case Dialect.SQLITE =>
assertTrue(json.contains(JsonDocument.four), s"Document 'four' not found ($json)")
assertTrue(json.contains(JsonDocument.five), s"Document 'five' not found ($json)")
case Dialect.POSTGRESQL =>
assertTrue(json.contains(docId("four")), s"Document 'four' not found ($json)")
assertTrue(json.contains(docId("five")), s"Document 'five' not found ($json)")
assertTrue(json.endsWith("]"), s"JSON should end with ']' ($json)")
def byContainsMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkByContainsMatch(db.conn.jsonByContains(TEST_TABLE, Map.Map1("value", "purple")))
def writeByContainsMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonByContains(TEST_TABLE, writer, Map.Map1("value", "purple"))
checkByContainsMatch(output.toString)
private def checkByContainsMatchOrdered(json: String): Unit =
Configuration.dialect() match
case Dialect.SQLITE =>
assertEquals(s"[${JsonDocument.two},${JsonDocument.four}]", json, "The documents were not ordered correctly")
case Dialect.POSTGRESQL =>
val twoIdx = json.indexOf(docId("two"))
val fourIdx = json.indexOf(docId("four"))
assertTrue(json.startsWith("["), s"JSON should start with '[' ($json)")
assertTrue(twoIdx >= 0, s"Document 'two' not found ($json)")
assertTrue(fourIdx >= 0, s"Document 'four' not found ($json)")
assertTrue(twoIdx < fourIdx, s"Document 'two' should have been before 'four' ($json)")
assertTrue(json.endsWith("]"), s"JSON should end with ']' ($json)")
def byContainsMatchOrdered(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkByContainsMatchOrdered(db.conn.jsonByContains(TEST_TABLE, Map.Map1("sub", Map.Map1("foo", "green")),
Field.named("value") :: Nil))
def writeByContainsMatchOrdered(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonByContains(TEST_TABLE, writer, Map.Map1("sub", Map.Map1("foo", "green")),
Field.named("value") :: Nil)
checkByContainsMatchOrdered(output.toString)
private def checkByContainsNoMatch(json: String): Unit =
assertEquals("[]", json, "There should have been no documents returned")
def byContainsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkByContainsNoMatch(db.conn.jsonByContains(TEST_TABLE, Map.Map1("value", "indigo")))
def writeByContainsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonByContains(TEST_TABLE, writer, Map.Map1("value", "indigo"))
checkByContainsNoMatch(output.toString)
private def checkByJsonPathMatch(json: String): Unit =
assertTrue(json.startsWith("["), s"JSON should start with '[' ($json)")
Configuration.dialect() match
case Dialect.SQLITE =>
assertTrue(json.contains(JsonDocument.four), s"Document 'four' not found ($json)")
assertTrue(json.contains(JsonDocument.five), s"Document 'five' not found ($json)")
case Dialect.POSTGRESQL =>
assertTrue(json.contains(docId("four")), s"Document 'four' not found ($json)")
assertTrue(json.contains(docId("five")), s"Document 'five' not found ($json)")
assertTrue(json.endsWith("]"), s"JSON should end with ']' ($json)")
def byJsonPathMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkByJsonPathMatch(db.conn.jsonByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)"))
def writeByJsonPathMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonByJsonPath(TEST_TABLE, writer, "$.numValue ? (@ > 10)")
checkByJsonPathMatch(output.toString)
private def checkByJsonPathMatchOrdered(json: String): Unit =
Configuration.dialect() match
case Dialect.SQLITE =>
assertEquals(s"[${JsonDocument.five},${JsonDocument.four}]", json, "The documents were not ordered correctly")
case Dialect.POSTGRESQL =>
val fiveIdx = json.indexOf(docId("five"))
val fourIdx = json.indexOf(docId("four"))
assertTrue(json.startsWith("["), s"JSON should start with '[' ($json)")
assertTrue(fiveIdx >= 0, s"Document 'five' not found ($json)")
assertTrue(fourIdx >= 0, s"Document 'four' not found ($json)")
assertTrue(fiveIdx < fourIdx, s"Document 'five' should have been before 'four' ($json)")
assertTrue(json.endsWith("]"), s"JSON should end with ']' ($json)")
def byJsonPathMatchOrdered(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkByJsonPathMatchOrdered(db.conn.jsonByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)", Field.named("id") :: Nil))
def writeByJsonPathMatchOrdered(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonByJsonPath(TEST_TABLE, writer, "$.numValue ? (@ > 10)", Field.named("id") :: Nil)
checkByJsonPathMatchOrdered(output.toString)
private def checkByJsonPathNoMatch(json: String): Unit =
assertEquals("[]", json, "There should have been no documents returned")
def byJsonPathNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkByJsonPathNoMatch(db.conn.jsonByJsonPath(TEST_TABLE, "$.numValue ? (@ > 100)"))
def writeByJsonPathNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonByJsonPath(TEST_TABLE, writer, "$.numValue ? (@ > 100)")
checkByJsonPathNoMatch(output.toString)
private def checkFirstByFieldsMatchOne(json: String): Unit =
Configuration.dialect() match
case Dialect.SQLITE => assertEquals(JsonDocument.two, json, "The incorrect document was returned")
case Dialect.POSTGRESQL => assertTrue(json.contains(docId("two")), s"The incorrect document was returned ($json)")
def firstByFieldsMatchOne(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkFirstByFieldsMatchOne(db.conn.jsonFirstByFields(TEST_TABLE, Field.equal("value", "another") :: Nil))
def writeFirstByFieldsMatchOne(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonFirstByFields(TEST_TABLE, writer, Field.equal("value", "another") :: Nil)
checkFirstByFieldsMatchOne(output.toString)
private def checkFirstByFieldsMatchMany(json: String): Unit =
Configuration.dialect() match
case Dialect.SQLITE =>
assertTrue(json.contains(JsonDocument.two) || json.contains(JsonDocument.four),
s"Expected document 'two' or 'four' ($json)")
case Dialect.POSTGRESQL =>
assertTrue(json.contains(docId("two")) || json.contains(docId("four")),
s"Expected document 'two' or 'four' ($json)")
def firstByFieldsMatchMany(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkFirstByFieldsMatchMany(db.conn.jsonFirstByFields(TEST_TABLE, Field.equal("sub.foo", "green") :: Nil))
def writeFirstByFieldsMatchMany(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonFirstByFields(TEST_TABLE, writer, Field.equal("sub.foo", "green") :: Nil)
checkFirstByFieldsMatchMany(output.toString)
private def checkFirstByFieldsMatchOrdered(json: String): Unit =
Configuration.dialect() match
case Dialect.SQLITE => assertEquals(JsonDocument.four, json, "An incorrect document was returned")
case Dialect.POSTGRESQL => assertTrue(json.contains(docId("four")), s"An incorrect document was returned ($json)")
def firstByFieldsMatchOrdered(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkFirstByFieldsMatchOrdered(db.conn.jsonFirstByFields(TEST_TABLE, Field.equal("sub.foo", "green") :: Nil, None,
Field.named("n:numValue DESC") :: Nil))
def writeFirstByFieldsMatchOrdered(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonFirstByFields(TEST_TABLE, writer, Field.equal("sub.foo", "green") :: Nil, None,
Field.named("n:numValue DESC") :: Nil)
private def checkFirstByFieldsNoMatch(json: String): Unit =
assertEquals("{}", json, "There should have been no document returned")
def firstByFieldsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkFirstByFieldsNoMatch(db.conn.jsonFirstByFields(TEST_TABLE, Field.equal("value", "absent") :: Nil))
def writeFirstByFieldsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonFirstByFields(TEST_TABLE, writer, Field.equal("value", "absent") :: Nil)
checkFirstByFieldsNoMatch(output.toString)
private def checkFirstByContainsMatchOne(json: String): Unit =
Configuration.dialect() match
case Dialect.SQLITE => assertEquals(JsonDocument.one, json, "An incorrect document was returned")
case Dialect.POSTGRESQL => assertTrue(json.contains(docId("one")), s"An incorrect document was returned ($json)")
def firstByContainsMatchOne(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkFirstByContainsMatchOne(db.conn.jsonFirstByContains(TEST_TABLE, Map.Map1("value", "FIRST!")))
def writeFirstByContainsMatchOne(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonFirstByContains(TEST_TABLE, writer, Map.Map1("value", "FIRST!"))
checkFirstByContainsMatchOne(output.toString)
private def checkFirstByContainsMatchMany(json: String): Unit =
Configuration.dialect() match
case Dialect.SQLITE =>
assertTrue(json.contains(JsonDocument.four) || json.contains(JsonDocument.five),
s"Expected document 'four' or 'five' ($json)")
case Dialect.POSTGRESQL =>
assertTrue(json.contains(docId("four")) || json.contains(docId("five")),
s"Expected document 'four' or 'five' ($json)")
def firstByContainsMatchMany(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkFirstByContainsMatchMany(db.conn.jsonFirstByContains(TEST_TABLE, Map.Map1("value", "purple")))
def writeFirstByContainsMatchMany(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonFirstByContains(TEST_TABLE, writer, Map.Map1("value", "purple"))
checkFirstByContainsMatchMany(output.toString)
private def checkFirstByContainsMatchOrdered(json: String): Unit =
Configuration.dialect() match
case Dialect.SQLITE => assertEquals(JsonDocument.five, json, "An incorrect document was returned")
case Dialect.POSTGRESQL => assertTrue(json.contains(docId("five")), s"An incorrect document was returned ($json)")
def firstByContainsMatchOrdered(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkFirstByContainsMatchOrdered(db.conn.jsonFirstByContains(TEST_TABLE, Map.Map1("value", "purple"),
Field.named("sub.bar NULLS FIRST") :: Nil))
def writeFirstByContainsMatchOrdered(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonFirstByContains(TEST_TABLE, writer, Map.Map1("value", "purple"),
Field.named("sub.bar NULLS FIRST") :: Nil)
checkFirstByContainsMatchOrdered(output.toString)
private def checkFirstByContainsNoMatch(json: String): Unit =
assertEquals("{}", json, "There should have been no document returned")
def firstByContainsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkFirstByContainsNoMatch(db.conn.jsonFirstByContains(TEST_TABLE, Map.Map1("value", "indigo")))
def writeFirstByContainsNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonFirstByContains(TEST_TABLE, writer, Map.Map1("value", "indigo"))
checkFirstByContainsNoMatch(output.toString)
private def checkFirstByJsonPathMatchOne(json: String): Unit =
Configuration.dialect() match
case Dialect.SQLITE => assertEquals(JsonDocument.two, json, "An incorrect document was returned")
case Dialect.POSTGRESQL => assertTrue(json.contains(docId("two")), s"An incorrect document was returned ($json)")
def firstByJsonPathMatchOne(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkFirstByJsonPathMatchOne(db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ == 10)"))
def writeFirstByJsonPathMatchOne(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonFirstByJsonPath(TEST_TABLE, writer, "$.numValue ? (@ == 10)")
checkFirstByJsonPathMatchOne(output.toString)
private def checkFirstByJsonPathMatchMany(json: String): Unit =
Configuration.dialect() match
case Dialect.SQLITE =>
assertTrue(json.contains(JsonDocument.four) || json.contains(JsonDocument.five),
s"Expected document 'four' or 'five' ($json)")
case Dialect.POSTGRESQL =>
assertTrue(json.contains(docId("four")) || json.contains(docId("five")),
s"Expected document 'four' or 'five' ($json)")
def firstByJsonPathMatchMany(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkFirstByJsonPathMatchMany(db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)"))
def writeFirstByJsonPathMatchMany(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonFirstByJsonPath(TEST_TABLE, writer, "$.numValue ? (@ > 10)")
checkFirstByJsonPathMatchMany(output.toString)
private def checkFirstByJsonPathMatchOrdered(json: String): Unit =
Configuration.dialect() match
case Dialect.SQLITE => assertEquals(JsonDocument.four, json, "An incorrect document was returned")
case Dialect.POSTGRESQL => assertTrue(json.contains(docId("four")), s"An incorrect document was returned ($json)")
def firstByJsonPathMatchOrdered(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkFirstByJsonPathMatchOrdered(db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)",
Field.named("id DESC") :: Nil))
def writeFirstByJsonPathMatchOrdered(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonFirstByJsonPath(TEST_TABLE, writer, "$.numValue ? (@ > 10)", Field.named("id DESC") :: Nil)
checkFirstByJsonPathMatchOrdered(output.toString)
private def checkFirstByJsonPathNoMatch(json: String): Unit =
assertEquals("{}", json, "There should have been no document returned")
def firstByJsonPathNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
checkFirstByJsonPathNoMatch(db.conn.jsonFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 100)"))
def writeFirstByJsonPathNoMatch(db: ThrowawayDatabase): Unit =
JsonDocument.load(db)
val output = StringWriter()
val writer = PrintWriter(output)
db.conn.writeJsonFirstByJsonPath(TEST_TABLE, writer, "$.numValue ? (@ > 100)")
checkFirstByJsonPathNoMatch(output.toString)

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,83 @@
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("jsonArray succeeds with empty array")
def jsonArrayEmpty(): Unit =
Using(PgDB()) { db => CustomFunctions.jsonArrayEmpty(db) }
@Test
@DisplayName("jsonArray succeeds with a single-item array")
def jsonArraySingle(): Unit =
Using(PgDB()) { db => CustomFunctions.jsonArraySingle(db) }
@Test
@DisplayName("jsonArray succeeds with a multi-item array")
def jsonArrayMany(): Unit =
Using(PgDB()) { db => CustomFunctions.jsonArrayMany(db) }
@Test
@DisplayName("writeJsonArray succeeds with empty array")
def writeJsonArrayEmpty(): Unit =
Using(PgDB()) { db => CustomFunctions.writeJsonArrayEmpty(db) }
@Test
@DisplayName("writeJsonArray succeeds with a single-item array")
def writeJsonArraySingle(): Unit =
Using(PgDB()) { db => CustomFunctions.writeJsonArraySingle(db) }
@Test
@DisplayName("writeJsonArray succeeds with a multi-item array")
def writeJsonArrayMany(): Unit =
Using(PgDB()) { db => CustomFunctions.writeJsonArrayMany(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("jsonSingle succeeds when document not found")
def jsonSingleNone(): Unit =
Using(PgDB()) { db => CustomFunctions.jsonSingleNone(db) }
@Test
@DisplayName("jsonSingle succeeds when a document is found")
def jsonSingleOne(): Unit =
Using(PgDB()) { db => CustomFunctions.jsonSingleOne(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,171 @@
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,301 @@
package solutions.bitbadger.documents.scala.tests.integration
import org.junit.jupiter.api.{DisplayName, Test}
import scala.util.Using
/**
* PostgreSQL integration tests for the `Json` object / `json*` connection extension functions
*/
@DisplayName("Scala | PostgreSQL: Json")
class PostgreSQLJsonIT:
@Test
@DisplayName("all retrieves all documents")
def allDefault(): Unit =
Using(PgDB()) { db => JsonFunctions.allDefault(db) }
@Test
@DisplayName("all succeeds with an empty table")
def allEmpty(): Unit =
Using(PgDB()) { db => JsonFunctions.allEmpty(db) }
@Test
@DisplayName("byId retrieves a document via a string ID")
def byIdString(): Unit =
Using(PgDB()) { db => JsonFunctions.byIdString(db) }
@Test
@DisplayName("byId retrieves a document via a numeric ID")
def byIdNumber(): Unit =
Using(PgDB()) { db => JsonFunctions.byIdNumber(db) }
@Test
@DisplayName("byId returns null when a matching ID is not found")
def byIdNotFound(): Unit =
Using(PgDB()) { db => JsonFunctions.byIdNotFound(db) }
@Test
@DisplayName("byFields retrieves matching documents")
def byFieldsMatch(): Unit =
Using(PgDB()) { db => JsonFunctions.byFieldsMatch(db) }
@Test
@DisplayName("byFields retrieves ordered matching documents")
def byFieldsMatchOrdered(): Unit =
Using(PgDB()) { db => JsonFunctions.byFieldsMatchOrdered(db) }
@Test
@DisplayName("byFields retrieves matching documents with a numeric IN clause")
def byFieldsMatchNumIn(): Unit =
Using(PgDB()) { db => JsonFunctions.byFieldsMatchNumIn(db) }
@Test
@DisplayName("byFields succeeds when no documents match")
def byFieldsNoMatch(): Unit =
Using(PgDB()) { db => JsonFunctions.byFieldsNoMatch(db) }
@Test
@DisplayName("byFields retrieves matching documents with an IN_ARRAY comparison")
def byFieldsMatchInArray(): Unit =
Using(PgDB()) { db => JsonFunctions.byFieldsMatchInArray(db) }
@Test
@DisplayName("byFields succeeds when no documents match an IN_ARRAY comparison")
def byFieldsNoMatchInArray(): Unit =
Using(PgDB()) { db => JsonFunctions.byFieldsNoMatchInArray(db) }
@Test
@DisplayName("byContains retrieves matching documents")
def byContainsMatch(): Unit =
Using(PgDB()) { db => JsonFunctions.byContainsMatch(db) }
@Test
@DisplayName("byContains retrieves ordered matching documents")
def byContainsMatchOrdered(): Unit =
Using(PgDB()) { db => JsonFunctions.byContainsMatchOrdered(db) }
@Test
@DisplayName("byContains succeeds when no documents match")
def byContainsNoMatch(): Unit =
Using(PgDB()) { db => JsonFunctions.byContainsNoMatch(db) }
@Test
@DisplayName("byJsonPath retrieves matching documents")
def byJsonPathMatch(): Unit =
Using(PgDB()) { db => JsonFunctions.byJsonPathMatch(db) }
@Test
@DisplayName("byJsonPath retrieves ordered matching documents")
def byJsonPathMatchOrdered(): Unit =
Using(PgDB()) { db => JsonFunctions.byJsonPathMatchOrdered(db) }
@Test
@DisplayName("byJsonPath succeeds when no documents match")
def byJsonPathNoMatch(): Unit =
Using(PgDB()) { db => JsonFunctions.byJsonPathNoMatch(db) }
@Test
@DisplayName("firstByFields retrieves a matching document")
def firstByFieldsMatchOne(): Unit =
Using(PgDB()) { db => JsonFunctions.firstByFieldsMatchOne(db) }
@Test
@DisplayName("firstByFields retrieves a matching document among many")
def firstByFieldsMatchMany(): Unit =
Using(PgDB()) { db => JsonFunctions.firstByFieldsMatchMany(db) }
@Test
@DisplayName("firstByFields retrieves a matching document among many (ordered)")
def firstByFieldsMatchOrdered(): Unit =
Using(PgDB()) { db => JsonFunctions.firstByFieldsMatchOrdered(db) }
@Test
@DisplayName("firstByFields returns null when no document matches")
def firstByFieldsNoMatch(): Unit =
Using(PgDB()) { db => JsonFunctions.firstByFieldsNoMatch(db) }
@Test
@DisplayName("firstByContains retrieves a matching document")
def firstByContainsMatchOne(): Unit =
Using(PgDB()) { db => JsonFunctions.firstByContainsMatchOne(db) }
@Test
@DisplayName("firstByContains retrieves a matching document among many")
def firstByContainsMatchMany(): Unit =
Using(PgDB()) { db => JsonFunctions.firstByContainsMatchMany(db) }
@Test
@DisplayName("firstByContains retrieves a matching document among many (ordered)")
def firstByContainsMatchOrdered(): Unit =
Using(PgDB()) { db => JsonFunctions.firstByContainsMatchOrdered(db) }
@Test
@DisplayName("firstByContains returns null when no document matches")
def firstByContainsNoMatch(): Unit =
Using(PgDB()) { db => JsonFunctions.firstByContainsNoMatch(db) }
@Test
@DisplayName("firstByJsonPath retrieves a matching document")
def firstByJsonPathMatchOne(): Unit =
Using(PgDB()) { db => JsonFunctions.firstByJsonPathMatchOne(db) }
@Test
@DisplayName("firstByJsonPath retrieves a matching document among many")
def firstByJsonPathMatchMany(): Unit =
Using(PgDB()) { db => JsonFunctions.firstByJsonPathMatchMany(db) }
@Test
@DisplayName("firstByJsonPath retrieves a matching document among many (ordered)")
def firstByJsonPathMatchOrdered(): Unit =
Using(PgDB()) { db => JsonFunctions.firstByJsonPathMatchOrdered(db) }
@Test
@DisplayName("firstByJsonPath returns null when no document matches")
def firstByJsonPathNoMatch(): Unit =
Using(PgDB()) { db => JsonFunctions.firstByJsonPathNoMatch(db) }
@Test
@DisplayName("writeAll retrieves all documents")
def writeAllDefault(): Unit =
Using(PgDB()) { db => JsonFunctions.writeAllDefault(db) }
@Test
@DisplayName("writeAll succeeds with an empty table")
def writeAllEmpty(): Unit =
Using(PgDB()) { db => JsonFunctions.writeAllEmpty(db) }
@Test
@DisplayName("writeById retrieves a document via a string ID")
def writeByIdString(): Unit =
Using(PgDB()) { db => JsonFunctions.writeByIdString(db) }
@Test
@DisplayName("writeById retrieves a document via a numeric ID")
def writeByIdNumber(): Unit =
Using(PgDB()) { db => JsonFunctions.writeByIdNumber(db) }
@Test
@DisplayName("writeById returns null when a matching ID is not found")
def writeByIdNotFound(): Unit =
Using(PgDB()) { db => JsonFunctions.writeByIdNotFound(db) }
@Test
@DisplayName("writeByFields retrieves matching documents")
def writeByFieldsMatch(): Unit =
Using(PgDB()) { db => JsonFunctions.writeByFieldsMatch(db) }
@Test
@DisplayName("writeByFields retrieves ordered matching documents")
def writeByFieldsMatchOrdered(): Unit =
Using(PgDB()) { db => JsonFunctions.writeByFieldsMatchOrdered(db) }
@Test
@DisplayName("writeByFields retrieves matching documents with a numeric IN clause")
def writeByFieldsMatchNumIn(): Unit =
Using(PgDB()) { db => JsonFunctions.writeByFieldsMatchNumIn(db) }
@Test
@DisplayName("writeByFields succeeds when no documents match")
def writeByFieldsNoMatch(): Unit =
Using(PgDB()) { db => JsonFunctions.writeByFieldsNoMatch(db) }
@Test
@DisplayName("writeByFields retrieves matching documents with an IN_ARRAY comparison")
def writeByFieldsMatchInArray(): Unit =
Using(PgDB()) { db => JsonFunctions.writeByFieldsMatchInArray(db) }
@Test
@DisplayName("writeByFields succeeds when no documents match an IN_ARRAY comparison")
def writeByFieldsNoMatchInArray(): Unit =
Using(PgDB()) { db => JsonFunctions.writeByFieldsNoMatchInArray(db) }
@Test
@DisplayName("writeByContains retrieves matching documents")
def writeByContainsMatch(): Unit =
Using(PgDB()) { db => JsonFunctions.writeByContainsMatch(db) }
@Test
@DisplayName("writeByContains retrieves ordered matching documents")
def writeByContainsMatchOrdered(): Unit =
Using(PgDB()) { db => JsonFunctions.writeByContainsMatchOrdered(db) }
@Test
@DisplayName("writeByContains succeeds when no documents match")
def writeByContainsNoMatch(): Unit =
Using(PgDB()) { db => JsonFunctions.writeByContainsNoMatch(db) }
@Test
@DisplayName("writeByJsonPath retrieves matching documents")
def writeByJsonPathMatch(): Unit =
Using(PgDB()) { db => JsonFunctions.writeByJsonPathMatch(db) }
@Test
@DisplayName("writeByJsonPath retrieves ordered matching documents")
def writeByJsonPathMatchOrdered(): Unit =
Using(PgDB()) { db => JsonFunctions.writeByJsonPathMatchOrdered(db) }
@Test
@DisplayName("writeByJsonPath succeeds when no documents match")
def writeByJsonPathNoMatch(): Unit =
Using(PgDB()) { db => JsonFunctions.writeByJsonPathNoMatch(db) }
@Test
@DisplayName("writeFirstByFields retrieves a matching document")
def writeFirstByFieldsMatchOne(): Unit =
Using(PgDB()) { db => JsonFunctions.writeFirstByFieldsMatchOne(db) }
@Test
@DisplayName("writeFirstByFields retrieves a matching document among many")
def writeFirstByFieldsMatchMany(): Unit =
Using(PgDB()) { db => JsonFunctions.writeFirstByFieldsMatchMany(db) }
@Test
@DisplayName("writeFirstByFields retrieves a matching document among many (ordered)")
def writeFirstByFieldsMatchOrdered(): Unit =
Using(PgDB()) { db => JsonFunctions.writeFirstByFieldsMatchOrdered(db) }
@Test
@DisplayName("writeFirstByFields returns null when no document matches")
def writeFirstByFieldsNoMatch(): Unit =
Using(PgDB()) { db => JsonFunctions.writeFirstByFieldsNoMatch(db) }
@Test
@DisplayName("writeFirstByContains retrieves a matching document")
def writeFirstByContainsMatchOne(): Unit =
Using(PgDB()) { db => JsonFunctions.writeFirstByContainsMatchOne(db) }
@Test
@DisplayName("writeFirstByContains retrieves a matching document among many")
def writeFirstByContainsMatchMany(): Unit =
Using(PgDB()) { db => JsonFunctions.writeFirstByContainsMatchMany(db) }
@Test
@DisplayName("writeFirstByContains retrieves a matching document among many (ordered)")
def writeFirstByContainsMatchOrdered(): Unit =
Using(PgDB()) { db => JsonFunctions.writeFirstByContainsMatchOrdered(db) }
@Test
@DisplayName("writeFirstByContains returns null when no document matches")
def writeFirstByContainsNoMatch(): Unit =
Using(PgDB()) { db => JsonFunctions.writeFirstByContainsNoMatch(db) }
@Test
@DisplayName("writeFirstByJsonPath retrieves a matching document")
def writeFirstByJsonPathMatchOne(): Unit =
Using(PgDB()) { db => JsonFunctions.writeFirstByJsonPathMatchOne(db) }
@Test
@DisplayName("writeFirstByJsonPath retrieves a matching document among many")
def writeFirstByJsonPathMatchMany(): Unit =
Using(PgDB()) { db => JsonFunctions.writeFirstByJsonPathMatchMany(db) }
@Test
@DisplayName("writeFirstByJsonPath retrieves a matching document among many (ordered)")
def writeFirstByJsonPathMatchOrdered(): Unit =
Using(PgDB()) { db => JsonFunctions.writeFirstByJsonPathMatchOrdered(db) }
@Test
@DisplayName("writeFirstByJsonPath returns null when no document matches")
def writeFirstByJsonPathNoMatch(): Unit =
Using(PgDB()) { db => JsonFunctions.writeFirstByJsonPathNoMatch(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,83 @@
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("jsonArray succeeds with empty array")
def jsonArrayEmpty(): Unit =
Using(SQLiteDB()) { db => CustomFunctions.jsonArrayEmpty(db) }
@Test
@DisplayName("jsonArray succeeds with a single-item array")
def jsonArraySingle(): Unit =
Using(SQLiteDB()) { db => CustomFunctions.jsonArraySingle(db) }
@Test
@DisplayName("jsonArray succeeds with a multi-item array")
def jsonArrayMany(): Unit =
Using(SQLiteDB()) { db => CustomFunctions.jsonArrayMany(db) }
@Test
@DisplayName("writeJsonArray succeeds with empty array")
def writeJsonArrayEmpty(): Unit =
Using(SQLiteDB()) { db => CustomFunctions.writeJsonArrayEmpty(db) }
@Test
@DisplayName("writeJsonArray succeeds with a single-item array")
def writeJsonArraySingle(): Unit =
Using(SQLiteDB()) { db => CustomFunctions.writeJsonArraySingle(db) }
@Test
@DisplayName("writeJsonArray succeeds with a multi-item array")
def writeJsonArrayMany(): Unit =
Using(SQLiteDB()) { db => CustomFunctions.writeJsonArrayMany(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("jsonSingle succeeds when document not found")
def jsonSingleNone(): Unit =
Using(SQLiteDB()) { db => CustomFunctions.jsonSingleNone(db) }
@Test
@DisplayName("jsonSingle succeeds when a document is found")
def jsonSingleOne(): Unit =
Using(SQLiteDB()) { db => CustomFunctions.jsonSingleOne(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,211 @@
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 `Json` object / `json*` connection extension functions
*/
@DisplayName("Scala | SQLite: Json")
class SQLiteJsonIT:
@Test
@DisplayName("all retrieves all documents")
def allDefault(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.allDefault(db) }
@Test
@DisplayName("all succeeds with an empty table")
def allEmpty(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.allEmpty(db) }
@Test
@DisplayName("byId retrieves a document via a string ID")
def byIdString(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.byIdString(db) }
@Test
@DisplayName("byId retrieves a document via a numeric ID")
def byIdNumber(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.byIdNumber(db) }
@Test
@DisplayName("byId returns null when a matching ID is not found")
def byIdNotFound(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.byIdNotFound(db) }
@Test
@DisplayName("byFields retrieves matching documents")
def byFieldsMatch(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.byFieldsMatch(db) }
@Test
@DisplayName("byFields retrieves ordered matching documents")
def byFieldsMatchOrdered(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.byFieldsMatchOrdered(db) }
@Test
@DisplayName("byFields retrieves matching documents with a numeric IN clause")
def byFieldsMatchNumIn(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.byFieldsMatchNumIn(db) }
@Test
@DisplayName("byFields succeeds when no documents match")
def byFieldsNoMatch(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.byFieldsNoMatch(db) }
@Test
@DisplayName("byFields retrieves matching documents with an IN_ARRAY comparison")
def byFieldsMatchInArray(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.byFieldsMatchInArray(db) }
@Test
@DisplayName("byFields succeeds when no documents match an IN_ARRAY comparison")
def byFieldsNoMatchInArray(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.byFieldsNoMatchInArray(db) }
@Test
@DisplayName("byContains fails")
def byContainsFails(): Unit =
Using(SQLiteDB()) { db => assertThrows(classOf[DocumentException], () => JsonFunctions.byContainsMatch(db)) }
@Test
@DisplayName("byJsonPath fails")
def byJsonPathFails(): Unit =
Using(SQLiteDB()) { db => assertThrows(classOf[DocumentException], () => JsonFunctions.byJsonPathMatch(db)) }
@Test
@DisplayName("firstByFields retrieves a matching document")
def firstByFieldsMatchOne(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.firstByFieldsMatchOne(db) }
@Test
@DisplayName("firstByFields retrieves a matching document among many")
def firstByFieldsMatchMany(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.firstByFieldsMatchMany(db) }
@Test
@DisplayName("firstByFields retrieves a matching document among many (ordered)")
def firstByFieldsMatchOrdered(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.firstByFieldsMatchOrdered(db) }
@Test
@DisplayName("firstByFields returns null when no document matches")
def firstByFieldsNoMatch(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.firstByFieldsNoMatch(db) }
@Test
@DisplayName("firstByContains fails")
def firstByContainsFails(): Unit =
Using(SQLiteDB()) { db =>
assertThrows(classOf[DocumentException], () => JsonFunctions.firstByContainsMatchOne(db))
}
@Test
@DisplayName("firstByJsonPath fails")
def firstByJsonPathFails(): Unit =
Using(SQLiteDB()) { db =>
assertThrows(classOf[DocumentException], () => JsonFunctions.firstByJsonPathMatchOne(db))
}
@Test
@DisplayName("writeAll retrieves all documents")
def writeAllDefault(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.writeAllDefault(db) }
@Test
@DisplayName("writeAll succeeds with an empty table")
def writeAllEmpty(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.writeAllEmpty(db) }
@Test
@DisplayName("writeById retrieves a document via a string ID")
def writeByIdString(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.writeByIdString(db) }
@Test
@DisplayName("writeById retrieves a document via a numeric ID")
def writeByIdNumber(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.writeByIdNumber(db) }
@Test
@DisplayName("writeById returns null when a matching ID is not found")
def writeByIdNotFound(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.writeByIdNotFound(db) }
@Test
@DisplayName("writeByFields retrieves matching documents")
def writeByFieldsMatch(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.writeByFieldsMatch(db) }
@Test
@DisplayName("writeByFields retrieves ordered matching documents")
def writeByFieldsMatchOrdered(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.writeByFieldsMatchOrdered(db) }
@Test
@DisplayName("writeByFields retrieves matching documents with a numeric IN clause")
def writeByFieldsMatchNumIn(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.writeByFieldsMatchNumIn(db) }
@Test
@DisplayName("writeByFields succeeds when no documents match")
def writeByFieldsNoMatch(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.writeByFieldsNoMatch(db) }
@Test
@DisplayName("writeByFields retrieves matching documents with an IN_ARRAY comparison")
def writeByFieldsMatchInArray(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.writeByFieldsMatchInArray(db) }
@Test
@DisplayName("writeByFields succeeds when no documents match an IN_ARRAY comparison")
def writeByFieldsNoMatchInArray(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.writeByFieldsNoMatchInArray(db) }
@Test
@DisplayName("writeByContains fails")
def writeByContainsFails(): Unit =
Using(SQLiteDB()) { db => assertThrows(classOf[DocumentException], () => JsonFunctions.writeByContainsMatch(db)) }
@Test
@DisplayName("writeByJsonPath fails")
def writeByJsonPathFails(): Unit =
Using(SQLiteDB()) { db => assertThrows(classOf[DocumentException], () => JsonFunctions.writeByJsonPathMatch(db)) }
@Test
@DisplayName("writeFirstByFields retrieves a matching document")
def writeFirstByFieldsMatchOne(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.writeFirstByFieldsMatchOne(db) }
@Test
@DisplayName("writeFirstByFields retrieves a matching document among many")
def writeFirstByFieldsMatchMany(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.writeFirstByFieldsMatchMany(db) }
@Test
@DisplayName("writeFirstByFields retrieves a matching document among many (ordered)")
def writeFirstByFieldsMatchOrdered(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.writeFirstByFieldsMatchOrdered(db) }
@Test
@DisplayName("writeFirstByFields returns null when no document matches")
def writeFirstByFieldsNoMatch(): Unit =
Using(SQLiteDB()) { db => JsonFunctions.writeFirstByFieldsNoMatch(db) }
@Test
@DisplayName("writeFirstByContains fails")
def writeFirstByContainsFails(): Unit =
Using(SQLiteDB()) { db =>
assertThrows(classOf[DocumentException], () => JsonFunctions.writeFirstByContainsMatchOne(db))
}
@Test
@DisplayName("writeFirstByJsonPath fails")
def writeFirstByJsonPathFails(): Unit =
Using(SQLiteDB()) { db =>
assertThrows(classOf[DocumentException], () => JsonFunctions.writeFirstByJsonPathMatchOne(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,3 @@
package solutions.bitbadger.documents.scala.tests
def TEST_TABLE = "test_table"