diff --git a/src/main/kotlin/Query.kt b/src/main/kotlin/Query.kt
index 6852f91..bb4774f 100644
--- a/src/main/kotlin/Query.kt
+++ b/src/main/kotlin/Query.kt
@@ -46,7 +46,7 @@ object Query {
         fun jsonContains(parameterName: String = ":criteria") =
             when (Configuration.dialect("create containment WHERE clause")) {
                 Dialect.POSTGRESQL -> "data @> $parameterName"
-                Dialect.SQLITE -> throw DocumentException("JSON containment is not supported")
+                Dialect.SQLITE     -> throw DocumentException("JSON containment is not supported")
             }
 
         /**
@@ -59,7 +59,7 @@ object Query {
         fun jsonPathMatches(parameterName: String = ":path") =
             when (Configuration.dialect("create JSON path match WHERE clause")) {
                 Dialect.POSTGRESQL -> "jsonb_path_exists(data, $parameterName::jsonpath)"
-                Dialect.SQLITE -> throw DocumentException("JSON path match is not supported")
+                Dialect.SQLITE     -> throw DocumentException("JSON path match is not supported")
             }
     }
 
@@ -77,12 +77,12 @@ object Query {
      * Create a query on JSON fields
      *
      * @param statement The SQL statement to be run against matching fields
-     * @param howMatched Whether to match any or all of the field conditions
      * @param fields The field conditions to be matched
+     * @param howMatched Whether to match any or all of the field conditions (optional; default ALL)
      * @return A query addressing documents by field matching conditions
      */
-    fun byFields(statement: String, howMatched: FieldMatch, fields: Collection<Field<*>>) =
-        Query.statementWhere(statement, Where.byFields(fields, howMatched))
+    fun byFields(statement: String, fields: Collection<Field<*>>, howMatched: FieldMatch? = null) =
+        statementWhere(statement, Where.byFields(fields, howMatched))
 
     /**
      * Functions to create queries to define tables and indexes
@@ -108,7 +108,7 @@ object Query {
         fun ensureTable(tableName: String) =
             when (Configuration.dialect("create table creation query")) {
                 Dialect.POSTGRESQL -> ensureTableFor(tableName, "JSONB")
-                Dialect.SQLITE -> ensureTableFor(tableName, "TEXT")
+                Dialect.SQLITE     -> ensureTableFor(tableName, "TEXT")
             }
 
         /**
@@ -117,10 +117,8 @@ object Query {
          * @param tableName The name of the table, possibly with a schema
          * @return A pair with the first item as the schema and the second as the table name
          */
-        private fun splitSchemaAndTable(tableName: String): Pair<String, String> {
-            val parts = tableName.split('.')
-            return if (parts.size == 1) Pair("", tableName) else Pair(parts[0], parts[1])
-        }
+        private fun splitSchemaAndTable(tableName: String) =
+            tableName.split('.').let { if (it.size == 1) Pair("", tableName) else Pair(it[0], it[1]) }
 
         /**
          * SQL statement to create an index on one or more fields in a JSON document
diff --git a/src/test/kotlin/QueryTest.kt b/src/test/kotlin/QueryTest.kt
index a437dd3..d8f6233 100644
--- a/src/test/kotlin/QueryTest.kt
+++ b/src/test/kotlin/QueryTest.kt
@@ -3,7 +3,9 @@ package solutions.bitbadger.documents
 import org.junit.jupiter.api.AfterEach
 import org.junit.jupiter.api.DisplayName
 import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
 import kotlin.test.assertEquals
+import kotlin.test.assertTrue
 
 class QueryTest {
 
@@ -130,6 +132,105 @@ class QueryTest {
         assertEquals("data->>'id' = :key", Query.Where.byId<String>(":key"))
     }
 
+    @Test
+    @DisplayName("Where.jsonContains generates defaults (PostgreSQL)")
+    fun whereJsonContainsDefaultPostgres() {
+        Configuration.connectionString = pg
+        assertEquals("data @> :criteria", Query.Where.jsonContains())
+    }
+
+    @Test
+    @DisplayName("Where.jsonContains generates named parameter (PostgreSQL)")
+    fun whereJsonContainsNamedPostgres() {
+        Configuration.connectionString = pg
+        assertEquals("data @> :it", Query.Where.jsonContains(":it"))
+    }
+
+    @Test
+    @DisplayName("Where.jsonContains fails (SQLite)")
+    fun whereJsonContainsFailsSQLite() {
+        Configuration.connectionString = lite
+        assertThrows<DocumentException> { Query.Where.jsonContains() }
+    }
+
+    @Test
+    @DisplayName("Where.jsonPathMatch generates defaults (PostgreSQL)")
+    fun whereJsonPathMatchDefaultPostgres() {
+        Configuration.connectionString = pg
+        assertEquals("jsonb_path_exists(data, :path::jsonpath)", Query.Where.jsonPathMatches())
+    }
+
+    @Test
+    @DisplayName("Where.jsonPathMatch generates named parameter (PostgreSQL)")
+    fun whereJsonPathMatchNamedPostgres() {
+        Configuration.connectionString = pg
+        assertEquals("jsonb_path_exists(data, :jp::jsonpath)", Query.Where.jsonPathMatches(":jp"))
+    }
+
+    @Test
+    @DisplayName("Where.jsonPathMatch fails (SQLite)")
+    fun whereJsonPathFailsSQLite() {
+        Configuration.connectionString = lite
+        assertThrows<DocumentException> { Query.Where.jsonPathMatches() }
+    }
+
+    // ~~~ root functions ~~~
+
+    @Test
+    @DisplayName("byId generates a numeric ID query (PostgreSQL)")
+    fun byIdNumericPostgres() {
+        Configuration.connectionString = pg
+        assertEquals("test WHERE (data->>'id')::numeric = :id", Query.byId("test", 9))
+    }
+
+    @Test
+    @DisplayName("byId generates an alphanumeric ID query (PostgreSQL)")
+    fun byIdAlphaPostgres() {
+        Configuration.connectionString = pg
+        assertEquals("unit WHERE data->>'id' = :id", Query.byId("unit", "18"))
+    }
+
+    @Test
+    @DisplayName("byId generates ID query (SQLite)")
+    fun byIdSQLite() {
+        Configuration.connectionString = lite
+        assertEquals("yo WHERE data->>'id' = :id", Query.byId("yo", 27))
+    }
+
+    @Test
+    @DisplayName("byFields generates default field query (PostgreSQL)")
+    fun byFieldsMultipleDefaultPostgres() {
+        Configuration.connectionString = pg
+        assertEquals("this WHERE data->>'a' = :the_a AND (data->>'b')::numeric = :b_value",
+            Query.byFields("this", listOf(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value"))))
+    }
+
+    @Test
+    @DisplayName("byFields generates default field query (SQLite)")
+    fun byFieldsMultipleDefaultSQLite() {
+        Configuration.connectionString = lite
+        assertEquals("this WHERE data->>'a' = :the_a AND data->>'b' = :b_value",
+            Query.byFields("this", listOf(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value"))))
+    }
+
+    @Test
+    @DisplayName("byFields generates ANY field query (PostgreSQL)")
+    fun byFieldsMultipleAnyPostgres() {
+        Configuration.connectionString = pg
+        assertEquals("that WHERE data->>'a' = :the_a OR (data->>'b')::numeric = :b_value",
+            Query.byFields("that", listOf(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")),
+                FieldMatch.ANY))
+    }
+
+    @Test
+    @DisplayName("byFields generates ANY field query (SQLite)")
+    fun byFieldsMultipleAnySQLite() {
+        Configuration.connectionString = lite
+        assertEquals("that WHERE data->>'a' = :the_a OR data->>'b' = :b_value",
+            Query.byFields("that", listOf(Field.equal("a", "", ":the_a"), Field.equal("b", 0, ":b_value")),
+                FieldMatch.ANY))
+    }
+
     // ~~~ Definition ~~~
 
     @Test
@@ -138,6 +239,26 @@ class QueryTest {
         assertEquals("CREATE TABLE IF NOT EXISTS my.table (data JSONB NOT NULL)",
             Query.Definition.ensureTableFor("my.table", "JSONB"), "CREATE TABLE statement not constructed correctly")
 
+    @Test
+    @DisplayName("Definition.ensureTable generates correctly (PostgreSQL)")
+    fun ensureTablePostgres() {
+        Configuration.connectionString = pg
+        assertEquals("CREATE TABLE IF NOT EXISTS $tbl (data JSONB NOT NULL)", Query.Definition.ensureTable(tbl))
+    }
+
+    @Test
+    @DisplayName("Definition.ensureTable generates correctly (SQLite)")
+    fun ensureTableSQLite() {
+        Configuration.connectionString = lite
+        assertEquals("CREATE TABLE IF NOT EXISTS $tbl (data TEXT NOT NULL)", Query.Definition.ensureTable(tbl))
+    }
+
+    @Test
+    @DisplayName("Definition.ensureTable fails when no dialect is set")
+    fun ensureTableFailsUnknown() {
+        assertThrows<DocumentException> { Query.Definition.ensureTable(tbl) }
+    }
+
     @Test
     @DisplayName("Definition.ensureKey generates correctly with schema")
     fun ensureKeyWithSchema() =
@@ -175,11 +296,97 @@ class QueryTest {
             Query.Definition.ensureIndexOn(tbl, "nest", listOf("a.b.c"), Dialect.SQLITE),
             "CREATE INDEX for nested SQLite field incorrect")
 
+    // ~~~ root functions ~~~
+
     @Test
-    @DisplayName("insert generates correctly")
-    fun insert() {
+    @DisplayName("insert generates no auto ID (PostgreSQL)")
+    fun insertNoAutoPostgres() {
         Configuration.connectionString = pg
-        assertEquals("INSERT INTO $tbl VALUES (:data)", Query.insert(tbl), "INSERT statement not constructed correctly")
+        assertEquals("INSERT INTO $tbl VALUES (:data)", Query.insert(tbl))
+    }
+
+    @Test
+    @DisplayName("insert generates no auto ID (SQLite)")
+    fun insertNoAutoSQLite() {
+        Configuration.connectionString = lite
+        assertEquals("INSERT INTO $tbl VALUES (:data)", Query.insert(tbl))
+    }
+
+    @Test
+    @DisplayName("insert generates auto number (PostgreSQL)")
+    fun insertAutoNumberPostgres() {
+        Configuration.connectionString = pg
+        assertEquals(
+            "INSERT INTO $tbl VALUES (:data::jsonb || ('{\"id\":' " +
+                    "|| (SELECT COALESCE(MAX((data->>'id')::numeric), 0) + 1 FROM $tbl) || '}')::jsonb)",
+            Query.insert(tbl, AutoId.NUMBER))
+    }
+
+    @Test
+    @DisplayName("insert generates auto number (SQLite)")
+    fun insertAutoNumberSQLite() {
+        Configuration.connectionString = lite
+        assertEquals(
+            "INSERT INTO $tbl VALUES (json_set(:data, '$.id', " +
+                    "(SELECT coalesce(max(data->>'id'), 0) + 1 FROM $tbl)))",
+            Query.insert(tbl, AutoId.NUMBER))
+    }
+
+    @Test
+    @DisplayName("insert generates auto UUID (PostgreSQL)")
+    fun insertAutoUUIDPostgres() {
+        Configuration.connectionString = pg
+        val query = Query.insert(tbl, AutoId.UUID)
+        assertTrue(query.startsWith("INSERT INTO $tbl VALUES (:data::jsonb || '{\"id\":\""),
+            "Query start not correct (actual: $query)")
+        assertTrue(query.endsWith("\"}')"), "Query end not correct")
+    }
+
+    @Test
+    @DisplayName("insert generates auto UUID (SQLite)")
+    fun insertAutoUUIDSQLite() {
+        Configuration.connectionString = lite
+        val query = Query.insert(tbl, AutoId.UUID)
+        assertTrue(query.startsWith("INSERT INTO $tbl VALUES (json_set(:data, '$.id', '"),
+            "Query start not correct (actual: $query)")
+        assertTrue(query.endsWith("'))"), "Query end not correct")
+    }
+
+    @Test
+    @DisplayName("insert generates auto random string (PostgreSQL)")
+    fun insertAutoRandomPostgres() {
+        try {
+            Configuration.connectionString = pg
+            Configuration.idStringLength   = 8
+            val query = Query.insert(tbl, AutoId.RANDOM_STRING)
+            assertTrue(query.startsWith("INSERT INTO $tbl VALUES (:data::jsonb || '{\"id\":\""),
+                "Query start not correct (actual: $query)")
+            assertTrue(query.endsWith("\"}')"), "Query end not correct")
+            assertEquals(8,
+                query.replace("INSERT INTO $tbl VALUES (:data::jsonb || '{\"id\":\"", "").replace("\"}')", "").length,
+                "Random string length incorrect")
+        } finally {
+            Configuration.idStringLength = 16
+        }
+    }
+
+    @Test
+    @DisplayName("insert generates auto random string (SQLite)")
+    fun insertAutoRandomSQLite() {
+        Configuration.connectionString = lite
+        val query = Query.insert(tbl, AutoId.RANDOM_STRING)
+        assertTrue(query.startsWith("INSERT INTO $tbl VALUES (json_set(:data, '$.id', '"),
+            "Query start not correct (actual: $query)")
+        assertTrue(query.endsWith("'))"), "Query end not correct")
+        assertEquals(Configuration.idStringLength,
+            query.replace("INSERT INTO $tbl VALUES (json_set(:data, '$.id', '", "").replace("'))", "").length,
+            "Random string length incorrect")
+    }
+
+    @Test
+    @DisplayName("insert fails when no dialect is set")
+    fun insertFailsUnknown() {
+        assertThrows<DocumentException> { Query.insert(tbl) }
     }
 
     @Test