import { tableForDatasource } from "../../../tests/utilities/structures"
import { datasourceDescribe } from "../../../integrations/tests/utils"
import {
  context,
  db as dbCore,
  docIds,
  MAX_VALID_DATE,
  MIN_VALID_DATE,
  setEnv,
  SQLITE_DESIGN_DOC_ID,
  utils,
  withEnv as withCoreEnv,
} from "@budibase/backend-core"

import {
  AIOperationEnum,
  AutoFieldSubType,
  BBReferenceFieldSubType,
  Datasource,
  EmptyFilterOption,
  FieldType,
  JsonFieldSubType,
  LogicalOperator,
  RelationshipType,
  RequiredKeys,
  Row,
  RowSearchParams,
  SearchFilters,
  SearchResponse,
  SearchRowRequest,
  SortOrder,
  SortType,
  Table,
  TableSchema,
  User,
  ViewV2Schema,
} from "@budibase/types"
import _ from "lodash"
import tk from "timekeeper"
import { encodeJSBinding } from "@budibase/string-templates"
import { dataFilters } from "@budibase/shared-core"
import { Knex } from "knex"
import { generator, structures, mocks } from "@budibase/backend-core/tests"
import { DEFAULT_EMPLOYEE_TABLE_SCHEMA } from "../../../db/defaultData/datasource_bb_default"
import { generateRowIdField } from "../../../integrations/utils"
import { cloneDeep } from "lodash/fp"
import { mockChatGPTResponse } from "../../../tests/utilities/mocks/openai"

const descriptions = datasourceDescribe({ plus: true })

if (descriptions.length) {
  describe.each(descriptions)(
    "search ($dbName)",
    ({ config, dsProvider, isInternal, isOracle, isSql }) => {
      let datasource: Datasource | undefined
      let client: Knex | undefined
      let tableOrViewId: string
      let rows: Row[]

      async function basicRelationshipTables(
        type: RelationshipType,
        opts?: {
          tableName?: string
          primaryColumn?: string
          otherColumn?: string
        }
      ) {
        const relatedTable = await createTable({
          name: { name: opts?.tableName || "name", type: FieldType.STRING },
        })

        const columnName = opts?.primaryColumn || "productCat"
        //@ts-ignore - API accepts this structure, will build out rest of definition
        const tableId = await createTable({
          name: { name: opts?.tableName || "name", type: FieldType.STRING },
          [columnName]: {
            type: FieldType.LINK,
            relationshipType: type,
            name: columnName,
            fieldName: opts?.otherColumn || "product",
            tableId: relatedTable,
            constraints: {
              type: "array",
            },
          },
        })
        return {
          relatedTable: await config.api.table.get(relatedTable),
          tableId,
        }
      }

      beforeAll(async () => {
        const ds = await dsProvider()
        datasource = ds.datasource
        client = ds.client

        config.app = await config.api.application.update(config.getAppId(), {
          snippets: [
            {
              name: "WeeksAgo",
              code: `
              return function (weeks) {
                const currentTime = new Date(${Date.now()});
                currentTime.setDate(currentTime.getDate()-(7 * (weeks || 1)));
                return currentTime.toISOString();
              }
            `,
            },
          ],
        })
      })

      async function createTable(schema?: TableSchema) {
        const table = await config.api.table.save(
          tableForDatasource(datasource, { schema })
        )
        return table._id!
      }

      async function createView(tableId: string, schema?: ViewV2Schema) {
        const view = await config.api.viewV2.create({
          tableId: tableId,
          name: generator.guid(),
          schema,
        })
        return view.id
      }

      async function createRows(arr: Record<string, any>[]) {
        // Shuffling to avoid false positives given a fixed order
        for (const row of _.shuffle(arr)) {
          await config.api.row.save(tableOrViewId, row)
        }
        rows = await config.api.row.fetch(tableOrViewId)
      }

      async function getTable(tableOrViewId: string): Promise<Table> {
        if (docIds.isViewId(tableOrViewId)) {
          const view = await config.api.viewV2.get(tableOrViewId)
          return await config.api.table.get(view.tableId)
        } else {
          return await config.api.table.get(tableOrViewId)
        }
      }

      async function assertTableExists(nameOrTable: string | Table) {
        const name =
          typeof nameOrTable === "string" ? nameOrTable : nameOrTable.name
        expect(await client!.schema.hasTable(name)).toBeTrue()
      }

      async function assertTableNumRows(
        nameOrTable: string | Table,
        numRows: number
      ) {
        const name =
          typeof nameOrTable === "string" ? nameOrTable : nameOrTable.name
        const row = await client!.from(name).count()
        const count = parseInt(Object.values(row[0])[0] as string)
        expect(count).toEqual(numRows)
      }

      describe.each([true, false])("in-memory: %s", isInMemory => {
        // We only run the in-memory tests during the SQS (isInternal) run
        if (isInMemory && !isInternal) {
          return
        }

        type CreateFn = (schema?: TableSchema) => Promise<string>
        let tableOrView: [string, CreateFn][] = [["table", createTable]]

        if (!isInMemory) {
          tableOrView.push([
            "view",
            async (schema?: TableSchema) => {
              const tableId = await createTable(schema)
              const viewId = await createView(
                tableId,
                Object.keys(schema || {}).reduce<ViewV2Schema>(
                  (viewSchema, fieldName) => {
                    const field = schema![fieldName]
                    viewSchema[fieldName] = {
                      visible: field.visible ?? true,
                      readonly: false,
                    }
                    return viewSchema
                  },
                  {}
                )
              )
              return viewId
            },
          ])
        }

        describe.each(tableOrView)(
          "from %s",
          (sourceType, createTableOrView) => {
            const isView = sourceType === "view"

            class SearchAssertion {
              constructor(private readonly query: SearchRowRequest) {}

              private async performSearch(): Promise<SearchResponse<Row>> {
                if (isInMemory) {
                  const inMemoryQuery: RequiredKeys<
                    Omit<RowSearchParams, "tableId">
                  > = {
                    sort: this.query.sort ?? undefined,
                    query: { ...this.query.query },
                    paginate: this.query.paginate,
                    bookmark: this.query.bookmark ?? undefined,
                    limit: this.query.limit,
                    sortOrder: this.query.sortOrder,
                    sortType: this.query.sortType ?? undefined,
                    version: this.query.version,
                    disableEscaping: this.query.disableEscaping,
                    countRows: this.query.countRows,
                    viewId: undefined,
                    fields: undefined,
                    indexer: undefined,
                    rows: undefined,
                  }
                  return dataFilters.search(_.cloneDeep(rows), inMemoryQuery)
                } else {
                  return config.api.row.search(tableOrViewId, this.query)
                }
              }

              // We originally used _.isMatch to compare rows, but found that when
              // comparing arrays it would return true if the source array was a subset of
              // the target array. This would sometimes create false matches. This
              // function is a more strict version of _.isMatch that only returns true if
              // the source array is an exact match of the target.
              //
              // _.isMatch("100", "1") also returns true which is not what we want.
              private isMatch<T extends Record<string, any>>(
                expected: T,
                found: T
              ) {
                if (!expected) {
                  throw new Error("Expected is undefined")
                }
                if (!found) {
                  return false
                }

                for (const key of Object.keys(expected)) {
                  if (Array.isArray(expected[key])) {
                    if (!Array.isArray(found[key])) {
                      return false
                    }
                    if (expected[key].length !== found[key].length) {
                      return false
                    }
                    if (!_.isMatch(found[key], expected[key])) {
                      return false
                    }
                  } else if (typeof expected[key] === "object") {
                    if (!this.isMatch(expected[key], found[key])) {
                      return false
                    }
                  } else {
                    if (expected[key] !== found[key]) {
                      return false
                    }
                  }
                }
                return true
              }

              // This function exists to ensure that the same row is not matched twice.
              // When a row gets matched, we make sure to remove it from the list of rows
              // we're matching against.
              private popRow<T extends { [key: string]: any }>(
                expectedRow: T,
                foundRows: T[]
              ): NonNullable<T> {
                const row = foundRows.find(row =>
                  this.isMatch(expectedRow, row)
                )
                if (!row) {
                  const fields = Object.keys(expectedRow)
                  // To make the error message more readable, we only include the fields
                  // that are present in the expected row.
                  const searchedObjects = foundRows.map(row =>
                    _.pick(row, fields)
                  )
                  throw new Error(
                    `Failed to find row:\n\n${JSON.stringify(
                      expectedRow,
                      null,
                      2
                    )}\n\nin\n\n${JSON.stringify(searchedObjects, null, 2)}`
                  )
                }

                foundRows.splice(foundRows.indexOf(row), 1)
                return row
              }

              // Asserts that the query returns rows matching exactly the set of rows
              // passed in. The order of the rows matters. Rows returned in an order
              // different to the one passed in will cause the assertion to fail.  Extra
              // rows returned by the query will also cause the assertion to fail.
              async toMatchExactly(expectedRows: any[]) {
                const response = await this.performSearch()
                const cloned = cloneDeep(response)
                const foundRows = response.rows

                expect(foundRows).toHaveLength(expectedRows.length)
                expect([...foundRows]).toEqual(
                  expectedRows.map((expectedRow: any) =>
                    expect.objectContaining(this.popRow(expectedRow, foundRows))
                  )
                )
                return cloned
              }

              // Asserts that the query returns rows matching exactly the set of rows
              // passed in. The order of the rows is not important, but extra rows will
              // cause the assertion to fail.
              async toContainExactly(expectedRows: any[]) {
                const response = await this.performSearch()
                const cloned = cloneDeep(response)
                const foundRows = response.rows

                expect(foundRows).toHaveLength(expectedRows.length)
                expect([...foundRows]).toEqual(
                  expect.arrayContaining(
                    expectedRows.map((expectedRow: any) =>
                      expect.objectContaining(
                        this.popRow(expectedRow, foundRows)
                      )
                    )
                  )
                )
                return cloned
              }

              // Asserts that the query returns some property values - this cannot be used
              // to check row values, however this shouldn't be important for checking properties
              // typing for this has to be any, Jest doesn't expose types for matchers like expect.any(...)
              async toMatch(properties: Record<string, any>) {
                const response = await this.performSearch()
                const cloned = cloneDeep(response)
                const keys = Object.keys(properties) as Array<
                  keyof SearchResponse<Row>
                >
                for (let key of keys) {
                  expect(response[key]).toBeDefined()
                  if (properties[key]) {
                    expect(response[key]).toEqual(properties[key])
                  }
                }
                return cloned
              }

              // Asserts that the query doesn't return a property, e.g. pagination parameters.
              async toNotHaveProperty(
                properties: (keyof SearchResponse<Row>)[]
              ) {
                const response = await this.performSearch()
                const cloned = cloneDeep(response)
                for (let property of properties) {
                  expect(response[property]).toBeUndefined()
                }
                return cloned
              }

              // Asserts that the query returns rows matching the set of rows passed in.
              // The order of the rows is not important. Extra rows will not cause the
              // assertion to fail.
              async toContain(expectedRows: any[]) {
                const response = await this.performSearch()
                const cloned = cloneDeep(response)
                const foundRows = response.rows

                expect([...foundRows]).toEqual(
                  expect.arrayContaining(
                    expectedRows.map((expectedRow: any) =>
                      expect.objectContaining(
                        this.popRow(expectedRow, foundRows)
                      )
                    )
                  )
                )
                return cloned
              }

              async toFindNothing() {
                await this.toContainExactly([])
              }

              async toHaveLength(length: number) {
                const { rows: foundRows } = await this.performSearch()

                expect(foundRows).toHaveLength(length)
              }
            }

            function expectSearch(query: SearchRowRequest) {
              return new SearchAssertion(query)
            }

            function expectQuery(query: SearchFilters) {
              return expectSearch({ query })
            }

            describe("boolean", () => {
              beforeAll(async () => {
                tableOrViewId = await createTableOrView({
                  isTrue: { name: "isTrue", type: FieldType.BOOLEAN },
                })
                await createRows([{ isTrue: true }, { isTrue: false }])
              })

              describe("equal", () => {
                it("successfully finds true row", async () => {
                  await expectQuery({ equal: { isTrue: true } }).toMatchExactly(
                    [{ isTrue: true }]
                  )
                })

                it("successfully finds false row", async () => {
                  await expectQuery({
                    equal: { isTrue: false },
                  }).toMatchExactly([{ isTrue: false }])
                })
              })

              describe("notEqual", () => {
                it("successfully finds false row", async () => {
                  await expectQuery({
                    notEqual: { isTrue: true },
                  }).toContainExactly([{ isTrue: false }])
                })

                it("successfully finds true row", async () => {
                  await expectQuery({
                    notEqual: { isTrue: false },
                  }).toContainExactly([{ isTrue: true }])
                })
              })

              describe("oneOf", () => {
                it("successfully finds true row", async () => {
                  await expectQuery({
                    oneOf: { isTrue: [true] },
                  }).toContainExactly([{ isTrue: true }])
                })

                it("successfully finds false row", async () => {
                  await expectQuery({
                    oneOf: { isTrue: [false] },
                  }).toContainExactly([{ isTrue: false }])
                })
              })

              describe("sort", () => {
                it("sorts ascending", async () => {
                  await expectSearch({
                    query: {},
                    sort: "isTrue",
                    sortOrder: SortOrder.ASCENDING,
                  }).toMatchExactly([{ isTrue: false }, { isTrue: true }])
                })

                it("sorts descending", async () => {
                  await expectSearch({
                    query: {},
                    sort: "isTrue",
                    sortOrder: SortOrder.DESCENDING,
                  }).toMatchExactly([{ isTrue: true }, { isTrue: false }])
                })
              })
            })

            !isInMemory &&
              describe("bindings", () => {
                let globalUsers: any = []

                const serverTime = new Date()

                // In MariaDB and MySQL we only store dates to second precision, so we need
                // to remove milliseconds from the server time to ensure searches work as
                // expected.
                serverTime.setMilliseconds(0)

                const future = new Date(
                  serverTime.getTime() + 1000 * 60 * 60 * 24 * 30
                )

                const rows = (currentUser: User) => {
                  return [
                    { name: "foo", appointment: "1982-01-05T00:00:00.000Z" },
                    { name: "bar", appointment: "1995-05-06T00:00:00.000Z" },
                    {
                      name: currentUser.firstName,
                      appointment: future.toISOString(),
                    },
                    {
                      name: "serverDate",
                      appointment: serverTime.toISOString(),
                    },
                    {
                      name: "single user, session user",
                      single_user: currentUser,
                    },
                    {
                      name: "single user",
                      single_user: globalUsers[0],
                    },
                    {
                      name: "deprecated single user, session user",
                      deprecated_single_user: [currentUser],
                    },
                    {
                      name: "deprecated single user",
                      deprecated_single_user: [globalUsers[0]],
                    },
                    {
                      name: "multi user",
                      multi_user: globalUsers,
                    },
                    {
                      name: "multi user with session user",
                      multi_user: [...globalUsers, currentUser],
                    },
                    {
                      name: "deprecated multi user",
                      deprecated_multi_user: globalUsers,
                    },
                    {
                      name: "deprecated multi user with session user",
                      deprecated_multi_user: [...globalUsers, currentUser],
                    },
                  ]
                }

                beforeAll(async () => {
                  // Set up some global users
                  globalUsers = await Promise.all(
                    Array(2)
                      .fill(0)
                      .map(async () => {
                        const globalUser = await config.globalUser()
                        const userMedataId = globalUser._id
                          ? dbCore.generateUserMetadataID(globalUser._id)
                          : null
                        return {
                          _id: globalUser._id,
                          _meta: userMedataId,
                        }
                      })
                  )

                  tableOrViewId = await createTableOrView({
                    name: { name: "name", type: FieldType.STRING },
                    appointment: {
                      name: "appointment",
                      type: FieldType.DATETIME,
                    },
                    single_user: {
                      name: "single_user",
                      type: FieldType.BB_REFERENCE_SINGLE,
                      subtype: BBReferenceFieldSubType.USER,
                    },
                    deprecated_single_user: {
                      name: "deprecated_single_user",
                      type: FieldType.BB_REFERENCE,
                      subtype: BBReferenceFieldSubType.USER,
                    },
                    multi_user: {
                      name: "multi_user",
                      type: FieldType.BB_REFERENCE,
                      subtype: BBReferenceFieldSubType.USER,
                      constraints: {
                        type: "array",
                      },
                    },
                    deprecated_multi_user: {
                      name: "deprecated_multi_user",
                      type: FieldType.BB_REFERENCE,
                      subtype: BBReferenceFieldSubType.USERS,
                      constraints: {
                        type: "array",
                      },
                    },
                  })
                  await createRows(rows(config.getUser()))
                })

                // !! Current User is auto generated per run
                it("should return all rows matching the session user firstname", async () => {
                  await expectQuery({
                    equal: { name: "{{ [user].firstName }}" },
                  }).toContainExactly([
                    {
                      name: config.getUser().firstName,
                      appointment: future.toISOString(),
                    },
                  ])
                })

                it("should return all rows matching the session user firstname when logical operator used", async () => {
                  await expectQuery({
                    $and: {
                      conditions: [
                        { equal: { name: "{{ [user].firstName }}" } },
                      ],
                    },
                  }).toContainExactly([
                    {
                      name: config.getUser().firstName,
                      appointment: future.toISOString(),
                    },
                  ])
                })

                it("should parse the date binding and return all rows after the resolved value", async () => {
                  await tk.withFreeze(serverTime, async () => {
                    await expectQuery({
                      range: {
                        appointment: {
                          low: "{{ [now] }}",
                          high: "9999-00-00T00:00:00.000Z",
                        },
                      },
                    }).toContainExactly([
                      {
                        name: config.getUser().firstName,
                        appointment: future.toISOString(),
                      },
                      {
                        name: "serverDate",
                        appointment: serverTime.toISOString(),
                      },
                    ])
                  })
                })

                it("should parse the date binding and return all rows before the resolved value", async () => {
                  await expectQuery({
                    range: {
                      appointment: {
                        low: "0000-00-00T00:00:00.000Z",
                        high: "{{ [now] }}",
                      },
                    },
                  }).toContainExactly([
                    { name: "foo", appointment: "1982-01-05T00:00:00.000Z" },
                    { name: "bar", appointment: "1995-05-06T00:00:00.000Z" },
                    {
                      name: "serverDate",
                      appointment: serverTime.toISOString(),
                    },
                  ])
                })

                it("should parse the encoded js snippet. Return rows with appointments up to 1 week in the past", async () => {
                  const jsBinding = "return snippets.WeeksAgo();"
                  const encodedBinding = encodeJSBinding(jsBinding)

                  await expectQuery({
                    range: {
                      appointment: {
                        low: "0000-00-00T00:00:00.000Z",
                        high: encodedBinding,
                      },
                    },
                  }).toContainExactly([
                    { name: "foo", appointment: "1982-01-05T00:00:00.000Z" },
                    { name: "bar", appointment: "1995-05-06T00:00:00.000Z" },
                  ])
                })

                it("should parse the encoded js binding. Return rows with appointments 2 weeks in the past", async () => {
                  const jsBinding = `const currentTime = new Date(${Date.now()})\ncurrentTime.setDate(currentTime.getDate()-14);\nreturn currentTime.toISOString();`
                  const encodedBinding = encodeJSBinding(jsBinding)

                  await expectQuery({
                    range: {
                      appointment: {
                        low: "0000-00-00T00:00:00.000Z",
                        high: encodedBinding,
                      },
                    },
                  }).toContainExactly([
                    { name: "foo", appointment: "1982-01-05T00:00:00.000Z" },
                    { name: "bar", appointment: "1995-05-06T00:00:00.000Z" },
                  ])
                })

                it("should match a single user row by the session user id", async () => {
                  await expectQuery({
                    equal: { single_user: "{{ [user]._id }}" },
                  }).toContainExactly([
                    {
                      name: "single user, session user",
                      single_user: { _id: config.getUser()._id },
                    },
                  ])
                })

                it("should match a deprecated single user row by the session user id", async () => {
                  await expectQuery({
                    equal: { deprecated_single_user: "{{ [user]._id }}" },
                  }).toContainExactly([
                    {
                      name: "deprecated single user, session user",
                      deprecated_single_user: [{ _id: config.getUser()._id }],
                    },
                  ])
                })

                it("should match the session user id in a multi user field", async () => {
                  const allUsers = [...globalUsers, config.getUser()].map(
                    (user: any) => {
                      return { _id: user._id }
                    }
                  )

                  await expectQuery({
                    contains: { multi_user: ["{{ [user]._id }}"] },
                  }).toContainExactly([
                    {
                      name: "multi user with session user",
                      multi_user: allUsers,
                    },
                  ])
                })

                it("should match the session user id in a deprecated multi user field", async () => {
                  const allUsers = [...globalUsers, config.getUser()].map(
                    (user: any) => {
                      return { _id: user._id }
                    }
                  )

                  await expectQuery({
                    contains: { deprecated_multi_user: ["{{ [user]._id }}"] },
                  }).toContainExactly([
                    {
                      name: "deprecated multi user with session user",
                      deprecated_multi_user: allUsers,
                    },
                  ])
                })

                it("should not match the session user id in a multi user field", async () => {
                  await expectQuery({
                    notContains: { multi_user: ["{{ [user]._id }}"] },
                    notEmpty: { multi_user: true },
                  }).toContainExactly([
                    {
                      name: "multi user",
                      multi_user: globalUsers.map((user: any) => {
                        return { _id: user._id }
                      }),
                    },
                  ])
                })

                it("should not match the session user id in a deprecated multi user field", async () => {
                  await expectQuery({
                    notContains: {
                      deprecated_multi_user: ["{{ [user]._id }}"],
                    },
                    notEmpty: { deprecated_multi_user: true },
                  }).toContainExactly([
                    {
                      name: "deprecated multi user",
                      deprecated_multi_user: globalUsers.map((user: any) => {
                        return { _id: user._id }
                      }),
                    },
                  ])
                })

                it("should match the session user id and a user table row id using helpers, user binding and a static user id.", async () => {
                  await expectQuery({
                    oneOf: {
                      single_user: [
                        "{{ default [user]._id '_empty_' }}",
                        globalUsers[0]._id,
                      ],
                    },
                  }).toContainExactly([
                    {
                      name: "single user, session user",
                      single_user: { _id: config.getUser()._id },
                    },
                    {
                      name: "single user",
                      single_user: { _id: globalUsers[0]._id },
                    },
                  ])
                })

                it("should match the session user id and a user table row id using helpers, user binding and a static user id. (deprecated single user)", async () => {
                  await expectQuery({
                    oneOf: {
                      deprecated_single_user: [
                        "{{ default [user]._id '_empty_' }}",
                        globalUsers[0]._id,
                      ],
                    },
                  }).toContainExactly([
                    {
                      name: "deprecated single user, session user",
                      deprecated_single_user: [{ _id: config.getUser()._id }],
                    },
                    {
                      name: "deprecated single user",
                      deprecated_single_user: [{ _id: globalUsers[0]._id }],
                    },
                  ])
                })

                it("should resolve 'default' helper to '_empty_' when binding resolves to nothing", async () => {
                  await expectQuery({
                    oneOf: {
                      single_user: [
                        "{{ default [user]._idx '_empty_' }}",
                        globalUsers[0]._id,
                      ],
                    },
                  }).toContainExactly([
                    {
                      name: "single user",
                      single_user: { _id: globalUsers[0]._id },
                    },
                  ])
                })

                it("should resolve 'default' helper to '_empty_' when binding resolves to nothing (deprecated single user)", async () => {
                  await expectQuery({
                    oneOf: {
                      deprecated_single_user: [
                        "{{ default [user]._idx '_empty_' }}",
                        globalUsers[0]._id,
                      ],
                    },
                  }).toContainExactly([
                    {
                      name: "deprecated single user",
                      deprecated_single_user: [{ _id: globalUsers[0]._id }],
                    },
                  ])
                })
              })

            const stringTypes = [FieldType.STRING, FieldType.LONGFORM] as const
            describe.each(stringTypes)("%s", type => {
              beforeAll(async () => {
                tableOrViewId = await createTableOrView({
                  name: { name: "name", type },
                })
                await createRows([{ name: "foo" }, { name: "bar" }])
              })

              describe("misc", () => {
                it("should return all if no query is passed", async () => {
                  await expectSearch({} as RowSearchParams).toContainExactly([
                    { name: "foo" },
                    { name: "bar" },
                  ])
                })

                it("should return all if empty query is passed", async () => {
                  await expectQuery({}).toContainExactly([
                    { name: "foo" },
                    { name: "bar" },
                  ])
                })

                it("should return all if onEmptyFilter is RETURN_ALL", async () => {
                  await expectQuery({
                    onEmptyFilter: EmptyFilterOption.RETURN_ALL,
                  }).toContainExactly([{ name: "foo" }, { name: "bar" }])
                })

                // onEmptyFilter cannot be sent to view searches
                !isView &&
                  it("should return nothing if onEmptyFilter is RETURN_NONE", async () => {
                    await expectQuery({
                      onEmptyFilter: EmptyFilterOption.RETURN_NONE,
                    }).toFindNothing()
                  })

                it("should respect limit", async () => {
                  await expectSearch({
                    limit: 1,
                    paginate: true,
                    query: {},
                  }).toHaveLength(1)
                })
              })

              describe("equal", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    equal: { name: "foo" },
                  }).toContainExactly([{ name: "foo" }])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({ equal: { name: "none" } }).toFindNothing()
                })

                it("works as an or condition", async () => {
                  await expectQuery({
                    allOr: true,
                    equal: { name: "foo" },
                    oneOf: { name: ["bar"] },
                  }).toContainExactly([{ name: "foo" }, { name: "bar" }])
                })

                it("can have multiple values for same column", async () => {
                  await expectQuery({
                    allOr: true,
                    equal: { "1:name": "foo", "2:name": "bar" },
                  }).toContainExactly([{ name: "foo" }, { name: "bar" }])
                })
              })

              describe("notEqual", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    notEqual: { name: "foo" },
                  }).toContainExactly([{ name: "bar" }])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({
                    notEqual: { name: "bar" },
                  }).toContainExactly([{ name: "foo" }])
                })
              })

              describe("oneOf", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    oneOf: { name: ["foo"] },
                  }).toContainExactly([{ name: "foo" }])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({
                    oneOf: { name: ["none"] },
                  }).toFindNothing()
                })

                it("can have multiple values for same column", async () => {
                  await expectQuery({
                    oneOf: {
                      name: ["foo", "bar"],
                    },
                  }).toContainExactly([{ name: "foo" }, { name: "bar" }])
                })

                it("splits comma separated strings", async () => {
                  await expectQuery({
                    oneOf: {
                      // @ts-ignore
                      name: "foo,bar",
                    },
                  }).toContainExactly([{ name: "foo" }, { name: "bar" }])
                })

                it("trims whitespace", async () => {
                  await expectQuery({
                    oneOf: {
                      // @ts-ignore
                      name: "foo, bar",
                    },
                  }).toContainExactly([{ name: "foo" }, { name: "bar" }])
                })

                it("empty arrays returns all when onEmptyFilter is set to return 'all'", async () => {
                  await expectQuery({
                    onEmptyFilter: EmptyFilterOption.RETURN_ALL,
                    oneOf: { name: [] },
                  }).toContainExactly([{ name: "foo" }, { name: "bar" }])
                })

                // onEmptyFilter cannot be sent to view searches
                !isView &&
                  it("empty arrays returns all when onEmptyFilter is set to return 'none'", async () => {
                    await expectQuery({
                      onEmptyFilter: EmptyFilterOption.RETURN_NONE,
                      oneOf: { name: [] },
                    }).toContainExactly([])
                  })
              })

              describe("fuzzy", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({ fuzzy: { name: "oo" } }).toContainExactly(
                    [{ name: "foo" }]
                  )
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({ fuzzy: { name: "none" } }).toFindNothing()
                })
              })

              describe("string", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    string: { name: "fo" },
                  }).toContainExactly([{ name: "foo" }])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({
                    string: { name: "none" },
                  }).toFindNothing()
                })

                it("is case-insensitive", async () => {
                  await expectQuery({
                    string: { name: "FO" },
                  }).toContainExactly([{ name: "foo" }])
                })

                it("should not coerce string to date for string columns", async () => {
                  await expectQuery({
                    string: { name: "2020-01-01" },
                  }).toFindNothing()
                })
              })

              describe("range", () => {
                it("successfully finds multiple rows", async () => {
                  await expectQuery({
                    range: { name: { low: "a", high: "z" } },
                  }).toContainExactly([{ name: "bar" }, { name: "foo" }])
                })

                it("successfully finds a row with a high bound", async () => {
                  await expectQuery({
                    range: { name: { low: "a", high: "c" } },
                  }).toContainExactly([{ name: "bar" }])
                })

                it("successfully finds a row with a low bound", async () => {
                  await expectQuery({
                    range: { name: { low: "f", high: "z" } },
                  }).toContainExactly([{ name: "foo" }])
                })

                it("successfully finds no rows", async () => {
                  await expectQuery({
                    range: { name: { low: "g", high: "h" } },
                  }).toFindNothing()
                })

                it("ignores low if it's an empty object", async () => {
                  await expectQuery({
                    // @ts-ignore
                    range: { name: { low: {}, high: "z" } },
                  }).toContainExactly([{ name: "foo" }, { name: "bar" }])
                })

                it("ignores high if it's an empty object", async () => {
                  await expectQuery({
                    // @ts-ignore
                    range: { name: { low: "a", high: {} } },
                  }).toContainExactly([{ name: "foo" }, { name: "bar" }])
                })
              })

              describe("empty", () => {
                it("finds no empty rows", async () => {
                  await expectQuery({ empty: { name: null } }).toFindNothing()
                })

                it("should not be affected by when filter empty behaviour", async () => {
                  await expectQuery({
                    empty: { name: null },
                    onEmptyFilter: EmptyFilterOption.RETURN_ALL,
                  }).toFindNothing()
                })
              })

              describe("notEmpty", () => {
                it("finds all non-empty rows", async () => {
                  await expectQuery({
                    notEmpty: { name: null },
                  }).toContainExactly([{ name: "foo" }, { name: "bar" }])
                })

                it("should not be affected by when filter empty behaviour", async () => {
                  await expectQuery({
                    notEmpty: { name: null },
                    onEmptyFilter: EmptyFilterOption.RETURN_NONE,
                  }).toContainExactly([{ name: "foo" }, { name: "bar" }])
                })
              })

              describe("sort", () => {
                it("sorts ascending", async () => {
                  await expectSearch({
                    query: {},
                    sort: "name",
                    sortOrder: SortOrder.ASCENDING,
                  }).toMatchExactly([{ name: "bar" }, { name: "foo" }])
                })

                it("sorts descending", async () => {
                  await expectSearch({
                    query: {},
                    sort: "name",
                    sortOrder: SortOrder.DESCENDING,
                  }).toMatchExactly([{ name: "foo" }, { name: "bar" }])
                })

                describe("sortType STRING", () => {
                  it("sorts ascending", async () => {
                    await expectSearch({
                      query: {},
                      sort: "name",
                      sortType: SortType.STRING,
                      sortOrder: SortOrder.ASCENDING,
                    }).toMatchExactly([{ name: "bar" }, { name: "foo" }])
                  })

                  it("sorts descending", async () => {
                    await expectSearch({
                      query: {},
                      sort: "name",
                      sortType: SortType.STRING,
                      sortOrder: SortOrder.DESCENDING,
                    }).toMatchExactly([{ name: "foo" }, { name: "bar" }])
                  })
                })

                !isInternal &&
                  !isInMemory &&
                  // This test was added because we automatically add in a sort by the
                  // primary key, and we used to do this unconditionally which caused
                  // problems because it was possible for the primary key to appear twice
                  // in the resulting SQL ORDER BY clause, resulting in an SQL error.
                  // We now check first to make sure that the primary key isn't already
                  // in the sort before adding it.
                  describe("sort on primary key", () => {
                    beforeAll(async () => {
                      const tableName = structures.uuid().substring(0, 10)
                      await client!.schema.createTable(tableName, t => {
                        t.string("name").primary()
                      })
                      const resp = await config.api.datasource.fetchSchema({
                        datasourceId: datasource!._id!,
                      })

                      tableOrViewId = resp.datasource.entities![tableName]._id!

                      await createRows([{ name: "foo" }, { name: "bar" }])
                    })

                    it("should be able to sort by a primary key column ascending", async () =>
                      expectSearch({
                        query: {},
                        sort: "name",
                        sortOrder: SortOrder.ASCENDING,
                      }).toMatchExactly([{ name: "bar" }, { name: "foo" }]))

                    it("should be able to sort by a primary key column descending", async () =>
                      expectSearch({
                        query: {},
                        sort: "name",
                        sortOrder: SortOrder.DESCENDING,
                      }).toMatchExactly([{ name: "foo" }, { name: "bar" }]))
                  })
              })
            })

            describe("numbers", () => {
              beforeAll(async () => {
                tableOrViewId = await createTableOrView({
                  age: { name: "age", type: FieldType.NUMBER },
                })
                await createRows([{ age: 1 }, { age: 10 }])
              })

              describe("equal", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({ equal: { age: 1 } }).toContainExactly([
                    { age: 1 },
                  ])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({ equal: { age: 2 } }).toFindNothing()
                })
              })

              describe("notEqual", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({ notEqual: { age: 1 } }).toContainExactly([
                    { age: 10 },
                  ])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({ notEqual: { age: 10 } }).toContainExactly(
                    [{ age: 1 }]
                  )
                })
              })

              describe("oneOf", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({ oneOf: { age: [1] } }).toContainExactly([
                    { age: 1 },
                  ])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({ oneOf: { age: [2] } }).toFindNothing()
                })

                it("can convert from a string", async () => {
                  await expectQuery({
                    oneOf: {
                      // @ts-ignore
                      age: "1",
                    },
                  }).toContainExactly([{ age: 1 }])
                })

                it("can find multiple values for same column", async () => {
                  await expectQuery({
                    oneOf: {
                      // @ts-ignore
                      age: "1,10",
                    },
                  }).toContainExactly([{ age: 1 }, { age: 10 }])
                })
              })

              describe("range", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    range: { age: { low: 1, high: 5 } },
                  }).toContainExactly([{ age: 1 }])
                })

                it("successfully finds multiple rows", async () => {
                  await expectQuery({
                    range: { age: { low: 1, high: 10 } },
                  }).toContainExactly([{ age: 1 }, { age: 10 }])
                })

                it("successfully finds a row with a high bound", async () => {
                  await expectQuery({
                    range: { age: { low: 5, high: 10 } },
                  }).toContainExactly([{ age: 10 }])
                })

                it("successfully finds no rows", async () => {
                  await expectQuery({
                    range: { age: { low: 5, high: 9 } },
                  }).toFindNothing()
                })

                it("greater than equal to", async () => {
                  await expectQuery({
                    range: {
                      age: { low: 10, high: Number.MAX_SAFE_INTEGER },
                    },
                  }).toContainExactly([{ age: 10 }])
                })

                it("greater than", async () => {
                  await expectQuery({
                    range: {
                      age: { low: 5, high: Number.MAX_SAFE_INTEGER },
                    },
                  }).toContainExactly([{ age: 10 }])
                })

                it("less than equal to", async () => {
                  await expectQuery({
                    range: {
                      age: { high: 1, low: Number.MIN_SAFE_INTEGER },
                    },
                  }).toContainExactly([{ age: 1 }])
                })

                it("less than", async () => {
                  await expectQuery({
                    range: {
                      age: { high: 5, low: Number.MIN_SAFE_INTEGER },
                    },
                  }).toContainExactly([{ age: 1 }])
                })
              })

              describe("sort", () => {
                it("sorts ascending", async () => {
                  await expectSearch({
                    query: {},
                    sort: "age",
                    sortOrder: SortOrder.ASCENDING,
                  }).toMatchExactly([{ age: 1 }, { age: 10 }])
                })

                it("sorts descending", async () => {
                  await expectSearch({
                    query: {},
                    sort: "age",
                    sortOrder: SortOrder.DESCENDING,
                  }).toMatchExactly([{ age: 10 }, { age: 1 }])
                })
              })

              describe("sortType NUMBER", () => {
                it("sorts ascending", async () => {
                  await expectSearch({
                    query: {},
                    sort: "age",
                    sortType: SortType.NUMBER,
                    sortOrder: SortOrder.ASCENDING,
                  }).toMatchExactly([{ age: 1 }, { age: 10 }])
                })

                it("sorts descending", async () => {
                  await expectSearch({
                    query: {},
                    sort: "age",
                    sortType: SortType.NUMBER,
                    sortOrder: SortOrder.DESCENDING,
                  }).toMatchExactly([{ age: 10 }, { age: 1 }])
                })
              })
            })

            describe("dates", () => {
              const JAN_1ST = "2020-01-01T00:00:00.000Z"
              const JAN_2ND = "2020-01-02T00:00:00.000Z"
              const JAN_5TH = "2020-01-05T00:00:00.000Z"
              const JAN_9TH = "2020-01-09T00:00:00.000Z"
              const JAN_10TH = "2020-01-10T00:00:00.000Z"

              beforeAll(async () => {
                tableOrViewId = await createTableOrView({
                  dob: { name: "dob", type: FieldType.DATETIME },
                })

                await createRows([{ dob: JAN_1ST }, { dob: JAN_10TH }])
              })

              describe("equal", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    equal: { dob: JAN_1ST },
                  }).toContainExactly([{ dob: JAN_1ST }])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({ equal: { dob: JAN_2ND } }).toFindNothing()
                })
              })

              describe("notEqual", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    notEqual: { dob: JAN_1ST },
                  }).toContainExactly([{ dob: JAN_10TH }])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({
                    notEqual: { dob: JAN_10TH },
                  }).toContainExactly([{ dob: JAN_1ST }])
                })
              })

              describe("oneOf", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    oneOf: { dob: [JAN_1ST] },
                  }).toContainExactly([{ dob: JAN_1ST }])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({
                    oneOf: { dob: [JAN_2ND] },
                  }).toFindNothing()
                })
              })

              describe("range", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    range: { dob: { low: JAN_1ST, high: JAN_5TH } },
                  }).toContainExactly([{ dob: JAN_1ST }])
                })

                it("successfully finds multiple rows", async () => {
                  await expectQuery({
                    range: { dob: { low: JAN_1ST, high: JAN_10TH } },
                  }).toContainExactly([{ dob: JAN_1ST }, { dob: JAN_10TH }])
                })

                it("successfully finds a row with a high bound", async () => {
                  await expectQuery({
                    range: { dob: { low: JAN_5TH, high: JAN_10TH } },
                  }).toContainExactly([{ dob: JAN_10TH }])
                })

                it("successfully finds no rows", async () => {
                  await expectQuery({
                    range: { dob: { low: JAN_5TH, high: JAN_9TH } },
                  }).toFindNothing()
                })

                it("greater than equal to", async () => {
                  await expectQuery({
                    range: {
                      dob: {
                        low: JAN_10TH,
                        high: MAX_VALID_DATE.toISOString(),
                      },
                    },
                  }).toContainExactly([{ dob: JAN_10TH }])
                })

                it("greater than", async () => {
                  await expectQuery({
                    range: {
                      dob: { low: JAN_5TH, high: MAX_VALID_DATE.toISOString() },
                    },
                  }).toContainExactly([{ dob: JAN_10TH }])
                })

                it("less than equal to", async () => {
                  await expectQuery({
                    range: {
                      dob: { high: JAN_1ST, low: MIN_VALID_DATE.toISOString() },
                    },
                  }).toContainExactly([{ dob: JAN_1ST }])
                })

                it("less than", async () => {
                  await expectQuery({
                    range: {
                      dob: { high: JAN_5TH, low: MIN_VALID_DATE.toISOString() },
                    },
                  }).toContainExactly([{ dob: JAN_1ST }])
                })
              })

              describe("sort", () => {
                it("sorts ascending", async () => {
                  await expectSearch({
                    query: {},
                    sort: "dob",
                    sortOrder: SortOrder.ASCENDING,
                  }).toMatchExactly([{ dob: JAN_1ST }, { dob: JAN_10TH }])
                })

                it("sorts descending", async () => {
                  await expectSearch({
                    query: {},
                    sort: "dob",
                    sortOrder: SortOrder.DESCENDING,
                  }).toMatchExactly([{ dob: JAN_10TH }, { dob: JAN_1ST }])
                })

                describe("sortType STRING", () => {
                  it("sorts ascending", async () => {
                    await expectSearch({
                      query: {},
                      sort: "dob",
                      sortType: SortType.STRING,
                      sortOrder: SortOrder.ASCENDING,
                    }).toMatchExactly([{ dob: JAN_1ST }, { dob: JAN_10TH }])
                  })

                  it("sorts descending", async () => {
                    await expectSearch({
                      query: {},
                      sort: "dob",
                      sortType: SortType.STRING,
                      sortOrder: SortOrder.DESCENDING,
                    }).toMatchExactly([{ dob: JAN_10TH }, { dob: JAN_1ST }])
                  })
                })
              })
            })

            !isInternal &&
              describe("datetime - time only", () => {
                const T_1000 = "10:00:00"
                const T_1045 = "10:45:00"
                const T_1200 = "12:00:00"
                const T_1530 = "15:30:00"
                const T_0000 = "00:00:00"

                const UNEXISTING_TIME = "10:01:00"

                const NULL_TIME__ID = `null_time__id`

                beforeAll(async () => {
                  tableOrViewId = await createTableOrView({
                    timeid: { name: "timeid", type: FieldType.STRING },
                    time: {
                      name: "time",
                      type: FieldType.DATETIME,
                      timeOnly: true,
                    },
                  })

                  await createRows([
                    { timeid: NULL_TIME__ID, time: null },
                    { time: T_1000 },
                    { time: T_1045 },
                    { time: T_1200 },
                    { time: T_1530 },
                    { time: T_0000 },
                  ])
                })

                describe("equal", () => {
                  it("successfully finds a row", async () => {
                    await expectQuery({
                      equal: { time: T_1000 },
                    }).toContainExactly([{ time: "10:00:00" }])
                  })

                  it("fails to find nonexistent row", async () => {
                    await expectQuery({
                      equal: { time: UNEXISTING_TIME },
                    }).toFindNothing()
                  })
                })

                describe("notEqual", () => {
                  it("successfully finds a row", async () => {
                    await expectQuery({
                      notEqual: { time: T_1000 },
                    }).toContainExactly([
                      { timeid: NULL_TIME__ID },
                      { time: "10:45:00" },
                      { time: "12:00:00" },
                      { time: "15:30:00" },
                      { time: "00:00:00" },
                    ])
                  })

                  it("return all when requesting non-existing", async () => {
                    await expectQuery({
                      notEqual: { time: UNEXISTING_TIME },
                    }).toContainExactly([
                      { timeid: NULL_TIME__ID },
                      { time: "10:00:00" },
                      { time: "10:45:00" },
                      { time: "12:00:00" },
                      { time: "15:30:00" },
                      { time: "00:00:00" },
                    ])
                  })
                })

                describe("oneOf", () => {
                  it("successfully finds a row", async () => {
                    await expectQuery({
                      oneOf: { time: [T_1000] },
                    }).toContainExactly([{ time: "10:00:00" }])
                  })

                  it("fails to find nonexistent row", async () => {
                    await expectQuery({
                      oneOf: { time: [UNEXISTING_TIME] },
                    }).toFindNothing()
                  })
                })

                describe("range", () => {
                  it("successfully finds a row", async () => {
                    await expectQuery({
                      range: { time: { low: T_1045, high: T_1045 } },
                    }).toContainExactly([{ time: "10:45:00" }])
                  })

                  it("successfully finds multiple rows", async () => {
                    await expectQuery({
                      range: { time: { low: T_1045, high: T_1530 } },
                    }).toContainExactly([
                      { time: "10:45:00" },
                      { time: "12:00:00" },
                      { time: "15:30:00" },
                    ])
                  })

                  it("successfully finds no rows", async () => {
                    await expectQuery({
                      range: {
                        time: { low: UNEXISTING_TIME, high: UNEXISTING_TIME },
                      },
                    }).toFindNothing()
                  })
                })

                describe("sort", () => {
                  it("sorts ascending", async () => {
                    await expectSearch({
                      query: {},
                      sort: "time",
                      sortOrder: SortOrder.ASCENDING,
                    }).toMatchExactly([
                      { timeid: NULL_TIME__ID },
                      { time: "00:00:00" },
                      { time: "10:00:00" },
                      { time: "10:45:00" },
                      { time: "12:00:00" },
                      { time: "15:30:00" },
                    ])
                  })

                  it("sorts descending", async () => {
                    await expectSearch({
                      query: {},
                      sort: "time",
                      sortOrder: SortOrder.DESCENDING,
                    }).toMatchExactly([
                      { time: "15:30:00" },
                      { time: "12:00:00" },
                      { time: "10:45:00" },
                      { time: "10:00:00" },
                      { time: "00:00:00" },
                      { timeid: NULL_TIME__ID },
                    ])
                  })

                  describe("sortType STRING", () => {
                    it("sorts ascending", async () => {
                      await expectSearch({
                        query: {},
                        sort: "time",
                        sortType: SortType.STRING,
                        sortOrder: SortOrder.ASCENDING,
                      }).toMatchExactly([
                        { timeid: NULL_TIME__ID },
                        { time: "00:00:00" },
                        { time: "10:00:00" },
                        { time: "10:45:00" },
                        { time: "12:00:00" },
                        { time: "15:30:00" },
                      ])
                    })

                    it("sorts descending", async () => {
                      await expectSearch({
                        query: {},
                        sort: "time",
                        sortType: SortType.STRING,
                        sortOrder: SortOrder.DESCENDING,
                      }).toMatchExactly([
                        { time: "15:30:00" },
                        { time: "12:00:00" },
                        { time: "10:45:00" },
                        { time: "10:00:00" },
                        { time: "00:00:00" },
                        { timeid: NULL_TIME__ID },
                      ])
                    })
                  })
                })
              })

            describe("datetime - date only", () => {
              describe.each([true, false])(
                "saved with timestamp: %s",
                saveWithTimestamp => {
                  describe.each([true, false])(
                    "search with timestamp: %s",
                    searchWithTimestamp => {
                      const SAVE_SUFFIX = saveWithTimestamp
                        ? "T00:00:00.000Z"
                        : ""
                      const SEARCH_SUFFIX = searchWithTimestamp
                        ? "T00:00:00.000Z"
                        : ""

                      const JAN_1ST = `2020-01-01`
                      const JAN_10TH = `2020-01-10`
                      const JAN_30TH = `2020-01-30`
                      const UNEXISTING_DATE = `2020-01-03`
                      const NULL_DATE__ID = `null_date__id`

                      beforeAll(async () => {
                        tableOrViewId = await createTableOrView({
                          dateid: {
                            name: "dateid",
                            type: FieldType.STRING,
                          },
                          date: {
                            name: "date",
                            type: FieldType.DATETIME,
                            dateOnly: true,
                          },
                        })

                        await createRows([
                          { dateid: NULL_DATE__ID, date: null },
                          { date: `${JAN_1ST}${SAVE_SUFFIX}` },
                          { date: `${JAN_10TH}${SAVE_SUFFIX}` },
                        ])
                      })

                      describe("equal", () => {
                        it("successfully finds a row", async () => {
                          await expectQuery({
                            equal: { date: `${JAN_1ST}${SEARCH_SUFFIX}` },
                          }).toContainExactly([{ date: JAN_1ST }])
                        })

                        it("successfully finds an ISO8601 row", async () => {
                          await expectQuery({
                            equal: { date: `${JAN_10TH}${SEARCH_SUFFIX}` },
                          }).toContainExactly([{ date: JAN_10TH }])
                        })

                        it("finds a row with ISO8601 timestamp", async () => {
                          await expectQuery({
                            equal: { date: `${JAN_1ST}${SEARCH_SUFFIX}` },
                          }).toContainExactly([{ date: JAN_1ST }])
                        })

                        it("fails to find nonexistent row", async () => {
                          await expectQuery({
                            equal: {
                              date: `${UNEXISTING_DATE}${SEARCH_SUFFIX}`,
                            },
                          }).toFindNothing()
                        })
                      })

                      describe("notEqual", () => {
                        it("successfully finds a row", async () => {
                          await expectQuery({
                            notEqual: {
                              date: `${JAN_1ST}${SEARCH_SUFFIX}`,
                            },
                          }).toContainExactly([
                            { date: JAN_10TH },
                            { dateid: NULL_DATE__ID },
                          ])
                        })

                        it("fails to find nonexistent row", async () => {
                          await expectQuery({
                            notEqual: {
                              date: `${JAN_30TH}${SEARCH_SUFFIX}`,
                            },
                          }).toContainExactly([
                            { date: JAN_1ST },
                            { date: JAN_10TH },
                            { dateid: NULL_DATE__ID },
                          ])
                        })
                      })

                      describe("oneOf", () => {
                        it("successfully finds a row", async () => {
                          await expectQuery({
                            oneOf: { date: [`${JAN_1ST}${SEARCH_SUFFIX}`] },
                          }).toContainExactly([{ date: JAN_1ST }])
                        })

                        it("fails to find nonexistent row", async () => {
                          await expectQuery({
                            oneOf: {
                              date: [`${UNEXISTING_DATE}${SEARCH_SUFFIX}`],
                            },
                          }).toFindNothing()
                        })
                      })

                      describe("range", () => {
                        it("successfully finds a row", async () => {
                          await expectQuery({
                            range: {
                              date: {
                                low: `${JAN_1ST}${SEARCH_SUFFIX}`,
                                high: `${JAN_1ST}${SEARCH_SUFFIX}`,
                              },
                            },
                          }).toContainExactly([{ date: JAN_1ST }])
                        })

                        it("successfully finds multiple rows", async () => {
                          await expectQuery({
                            range: {
                              date: {
                                low: `${JAN_1ST}${SEARCH_SUFFIX}`,
                                high: `${JAN_10TH}${SEARCH_SUFFIX}`,
                              },
                            },
                          }).toContainExactly([
                            { date: JAN_1ST },
                            { date: JAN_10TH },
                          ])
                        })

                        it("successfully finds no rows", async () => {
                          await expectQuery({
                            range: {
                              date: {
                                low: `${JAN_30TH}${SEARCH_SUFFIX}`,
                                high: `${JAN_30TH}${SEARCH_SUFFIX}`,
                              },
                            },
                          }).toFindNothing()
                        })
                      })

                      describe("sort", () => {
                        it("sorts ascending", async () => {
                          await expectSearch({
                            query: {},
                            sort: "date",
                            sortOrder: SortOrder.ASCENDING,
                          }).toMatchExactly([
                            { dateid: NULL_DATE__ID },
                            { date: JAN_1ST },
                            { date: JAN_10TH },
                          ])
                        })

                        it("sorts descending", async () => {
                          await expectSearch({
                            query: {},
                            sort: "date",
                            sortOrder: SortOrder.DESCENDING,
                          }).toMatchExactly([
                            { date: JAN_10TH },
                            { date: JAN_1ST },
                            { dateid: NULL_DATE__ID },
                          ])
                        })

                        describe("sortType STRING", () => {
                          it("sorts ascending", async () => {
                            await expectSearch({
                              query: {},
                              sort: "date",
                              sortType: SortType.STRING,
                              sortOrder: SortOrder.ASCENDING,
                            }).toMatchExactly([
                              { dateid: NULL_DATE__ID },
                              { date: JAN_1ST },
                              { date: JAN_10TH },
                            ])
                          })

                          it("sorts descending", async () => {
                            await expectSearch({
                              query: {},
                              sort: "date",
                              sortType: SortType.STRING,
                              sortOrder: SortOrder.DESCENDING,
                            }).toMatchExactly([
                              { date: JAN_10TH },
                              { date: JAN_1ST },
                              { dateid: NULL_DATE__ID },
                            ])
                          })
                        })
                      })
                    }
                  )
                }
              )
            })

            isInternal &&
              !isInMemory &&
              describe("AI Column", () => {
                const UNEXISTING_AI_COLUMN = "Real LLM Response"
                let envCleanup: () => void

                beforeAll(async () => {
                  mocks.licenses.useBudibaseAI()
                  mocks.licenses.useAICustomConfigs()

                  envCleanup = setEnv({ OPENAI_API_KEY: "mock" })
                  mockChatGPTResponse("Mock LLM Response")

                  tableOrViewId = await createTableOrView({
                    product: { name: "product", type: FieldType.STRING },
                    ai: {
                      name: "AI",
                      type: FieldType.AI,
                      operation: AIOperationEnum.PROMPT,
                      prompt: "Translate '{{ product }}' into German",
                    },
                  })

                  await createRows([
                    { product: "Big Mac" },
                    { product: "McCrispy" },
                  ])
                })

                afterAll(() => {
                  envCleanup()
                })

                describe("equal", () => {
                  it("successfully finds rows based on AI column", async () => {
                    await expectQuery({
                      equal: { ai: "Mock LLM Response" },
                    }).toContainExactly([
                      { product: "Big Mac" },
                      { product: "McCrispy" },
                    ])
                  })

                  it("fails to find nonexistent row", async () => {
                    await expectQuery({
                      equal: { ai: UNEXISTING_AI_COLUMN },
                    }).toFindNothing()
                  })
                })

                describe("notEqual", () => {
                  it("Returns nothing when searching notEqual on the mock AI response", async () => {
                    await expectQuery({
                      notEqual: { ai: "Mock LLM Response" },
                    }).toContainExactly([])
                  })

                  it("return all when requesting non-existing response", async () => {
                    await expectQuery({
                      notEqual: { ai: "Real LLM Response" },
                    }).toContainExactly([
                      { product: "Big Mac" },
                      { product: "McCrispy" },
                    ])
                  })
                })

                describe("oneOf", () => {
                  it("successfully finds a row", async () => {
                    await expectQuery({
                      oneOf: {
                        ai: ["Mock LLM Response", "Other LLM Response"],
                      },
                    }).toContainExactly([
                      { product: "Big Mac" },
                      { product: "McCrispy" },
                    ])
                  })

                  it("fails to find nonexistent row", async () => {
                    await expectQuery({
                      oneOf: { ai: ["Whopper"] },
                    }).toFindNothing()
                  })
                })
              })

            describe("arrays", () => {
              beforeAll(async () => {
                tableOrViewId = await createTableOrView({
                  numbers: {
                    name: "numbers",
                    type: FieldType.ARRAY,
                    constraints: {
                      type: JsonFieldSubType.ARRAY,
                      inclusion: ["one", "two", "three"],
                    },
                  },
                })
                await createRows([
                  { numbers: ["one", "two"] },
                  { numbers: ["three"] },
                ])
              })

              describe("contains", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    contains: { numbers: ["one"] },
                  }).toContainExactly([{ numbers: ["one", "two"] }])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({
                    contains: { numbers: ["none"] },
                  }).toFindNothing()
                })

                it("fails to find row containing all", async () => {
                  await expectQuery({
                    contains: { numbers: ["one", "two", "three"] },
                  }).toFindNothing()
                })

                it("finds all with empty list", async () => {
                  await expectQuery({
                    contains: { numbers: [] },
                  }).toContainExactly([
                    { numbers: ["one", "two"] },
                    { numbers: ["three"] },
                  ])
                })
              })

              describe("notContains", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    notContains: { numbers: ["one"] },
                  }).toContainExactly([{ numbers: ["three"] }])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({
                    notContains: { numbers: ["one", "two", "three"] },
                  }).toContainExactly([
                    { numbers: ["one", "two"] },
                    { numbers: ["three"] },
                  ])
                })

                // Not sure if this is correct behaviour but changing it would be a
                // breaking change.
                it("finds all with empty list", async () => {
                  await expectQuery({
                    notContains: { numbers: [] },
                  }).toContainExactly([
                    { numbers: ["one", "two"] },
                    { numbers: ["three"] },
                  ])
                })
              })

              describe("containsAny", () => {
                it("successfully finds rows", async () => {
                  await expectQuery({
                    containsAny: { numbers: ["one", "two", "three"] },
                  }).toContainExactly([
                    { numbers: ["one", "two"] },
                    { numbers: ["three"] },
                  ])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({
                    containsAny: { numbers: ["none"] },
                  }).toFindNothing()
                })

                it("finds all with empty list", async () => {
                  await expectQuery({
                    containsAny: { numbers: [] },
                  }).toContainExactly([
                    { numbers: ["one", "two"] },
                    { numbers: ["three"] },
                  ])
                })
              })
            })

            describe("bigints", () => {
              const SMALL = "1"
              const MEDIUM = "10000000"

              // Our bigints are int64s in most datasources.
              let BIG = "9223372036854775807"

              beforeAll(async () => {
                tableOrViewId = await createTableOrView({
                  num: { name: "num", type: FieldType.BIGINT },
                })
                await createRows([
                  { num: SMALL },
                  { num: MEDIUM },
                  { num: BIG },
                ])
              })

              describe("equal", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({ equal: { num: SMALL } }).toContainExactly(
                    [{ num: SMALL }]
                  )
                })

                it("successfully finds a big value", async () => {
                  await expectQuery({ equal: { num: BIG } }).toContainExactly([
                    { num: BIG },
                  ])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({ equal: { num: "2" } }).toFindNothing()
                })
              })

              describe("notEqual", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    notEqual: { num: SMALL },
                  }).toContainExactly([{ num: MEDIUM }, { num: BIG }])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({ notEqual: { num: 10 } }).toContainExactly(
                    [{ num: SMALL }, { num: MEDIUM }, { num: BIG }]
                  )
                })
              })

              describe("oneOf", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    oneOf: { num: [SMALL] },
                  }).toContainExactly([{ num: SMALL }])
                })

                it("successfully finds all rows", async () => {
                  await expectQuery({
                    oneOf: { num: [SMALL, MEDIUM, BIG] },
                  }).toContainExactly([
                    { num: SMALL },
                    { num: MEDIUM },
                    { num: BIG },
                  ])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({ oneOf: { num: [2] } }).toFindNothing()
                })
              })

              describe("range", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    range: { num: { low: SMALL, high: "5" } },
                  }).toContainExactly([{ num: SMALL }])
                })

                it("successfully finds multiple rows", async () => {
                  await expectQuery({
                    range: { num: { low: SMALL, high: MEDIUM } },
                  }).toContainExactly([{ num: SMALL }, { num: MEDIUM }])
                })

                it("successfully finds a row with a high bound", async () => {
                  await expectQuery({
                    range: { num: { low: MEDIUM, high: BIG } },
                  }).toContainExactly([{ num: MEDIUM }, { num: BIG }])
                })

                it("successfully finds no rows", async () => {
                  await expectQuery({
                    range: { num: { low: "5", high: "5" } },
                  }).toFindNothing()
                })

                it("can search using just a low value", async () => {
                  await expectQuery({
                    range: { num: { low: MEDIUM } },
                  }).toContainExactly([{ num: MEDIUM }, { num: BIG }])
                })

                it("can search using just a high value", async () => {
                  await expectQuery({
                    range: { num: { high: MEDIUM } },
                  }).toContainExactly([{ num: SMALL }, { num: MEDIUM }])
                })
              })
            })

            isInternal &&
              describe("auto", () => {
                beforeAll(async () => {
                  tableOrViewId = await createTableOrView({
                    auto: {
                      name: "auto",
                      type: FieldType.AUTO,
                      autocolumn: true,
                      subtype: AutoFieldSubType.AUTO_ID,
                    },
                  })
                  await createRows(new Array(10).fill({}))
                })

                describe("equal", () => {
                  it("successfully finds a row", async () => {
                    await expectQuery({ equal: { auto: 1 } }).toContainExactly([
                      { auto: 1 },
                    ])
                  })

                  it("fails to find nonexistent row", async () => {
                    await expectQuery({ equal: { auto: 0 } }).toFindNothing()
                  })
                })

                describe("not equal", () => {
                  it("successfully finds a row", async () => {
                    await expectQuery({
                      notEqual: { auto: 1 },
                    }).toContainExactly([
                      { auto: 2 },
                      { auto: 3 },
                      { auto: 4 },
                      { auto: 5 },
                      { auto: 6 },
                      { auto: 7 },
                      { auto: 8 },
                      { auto: 9 },
                      { auto: 10 },
                    ])
                  })

                  it("fails to find nonexistent row", async () => {
                    await expectQuery({
                      notEqual: { auto: 0 },
                    }).toContainExactly([
                      { auto: 1 },
                      { auto: 2 },
                      { auto: 3 },
                      { auto: 4 },
                      { auto: 5 },
                      { auto: 6 },
                      { auto: 7 },
                      { auto: 8 },
                      { auto: 9 },
                      { auto: 10 },
                    ])
                  })
                })

                describe("oneOf", () => {
                  it("successfully finds a row", async () => {
                    await expectQuery({
                      oneOf: { auto: [1] },
                    }).toContainExactly([{ auto: 1 }])
                  })

                  it("fails to find nonexistent row", async () => {
                    await expectQuery({ oneOf: { auto: [0] } }).toFindNothing()
                  })
                })

                describe("range", () => {
                  it("successfully finds a row", async () => {
                    await expectQuery({
                      range: { auto: { low: 1, high: 1 } },
                    }).toContainExactly([{ auto: 1 }])
                  })

                  it("successfully finds multiple rows", async () => {
                    await expectQuery({
                      range: { auto: { low: 1, high: 2 } },
                    }).toContainExactly([{ auto: 1 }, { auto: 2 }])
                  })

                  it("successfully finds a row with a high bound", async () => {
                    await expectQuery({
                      range: { auto: { low: 2, high: 2 } },
                    }).toContainExactly([{ auto: 2 }])
                  })

                  it("successfully finds no rows", async () => {
                    await expectQuery({
                      range: { auto: { low: 0, high: 0 } },
                    }).toFindNothing()
                  })

                  it("can search using just a low value", async () => {
                    await expectQuery({
                      range: { auto: { low: 9 } },
                    }).toContainExactly([{ auto: 9 }, { auto: 10 }])
                  })

                  it("can search using just a high value", async () => {
                    await expectQuery({
                      range: { auto: { high: 2 } },
                    }).toContainExactly([{ auto: 1 }, { auto: 2 }])
                  })
                })

                describe("sort", () => {
                  it("sorts ascending", async () => {
                    await expectSearch({
                      query: {},
                      sort: "auto",
                      sortOrder: SortOrder.ASCENDING,
                      sortType: SortType.NUMBER,
                    }).toMatchExactly([
                      { auto: 1 },
                      { auto: 2 },
                      { auto: 3 },
                      { auto: 4 },
                      { auto: 5 },
                      { auto: 6 },
                      { auto: 7 },
                      { auto: 8 },
                      { auto: 9 },
                      { auto: 10 },
                    ])
                  })

                  it("sorts descending", async () => {
                    await expectSearch({
                      query: {},
                      sort: "auto",
                      sortOrder: SortOrder.DESCENDING,
                      sortType: SortType.NUMBER,
                    }).toMatchExactly([
                      { auto: 10 },
                      { auto: 9 },
                      { auto: 8 },
                      { auto: 7 },
                      { auto: 6 },
                      { auto: 5 },
                      { auto: 4 },
                      { auto: 3 },
                      { auto: 2 },
                      { auto: 1 },
                    ])
                  })

                  // This is important for pagination. The order of results must always
                  // be stable or pagination will break. We don't want the user to need
                  // to specify an order for pagination to work.
                  it("is stable without a sort specified", async () => {
                    let { rows: fullRowList } = await config.api.row.search(
                      tableOrViewId,
                      {
                        tableId: tableOrViewId,
                        query: {},
                      }
                    )

                    // repeat the search many times to check the first row is always the same
                    let bookmark: string | number | undefined,
                      hasNextPage: boolean | undefined = true,
                      rowCount = 0
                    do {
                      const response = await config.api.row.search(
                        tableOrViewId,
                        {
                          tableId: tableOrViewId,
                          limit: 1,
                          paginate: true,
                          query: {},
                          bookmark,
                        }
                      )
                      bookmark = response.bookmark
                      hasNextPage = response.hasNextPage
                      expect(response.rows.length).toEqual(1)
                      const foundRow = response.rows[0]
                      expect(foundRow).toEqual(fullRowList[rowCount++])
                    } while (hasNextPage)
                  })
                })

                describe("pagination", () => {
                  it("should paginate through all rows", async () => {
                    // @ts-ignore
                    let bookmark: string | number = undefined
                    let rows: Row[] = []

                    while (true) {
                      const response = await config.api.row.search(
                        tableOrViewId,
                        {
                          tableId: tableOrViewId,
                          limit: 3,
                          query: {},
                          bookmark,
                          paginate: true,
                        }
                      )

                      rows.push(...response.rows)

                      if (!response.bookmark || !response.hasNextPage) {
                        break
                      }
                      bookmark = response.bookmark
                    }

                    const autoValues = rows
                      .map(row => row.auto)
                      .sort((a, b) => a - b)
                    expect(autoValues).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
                  })
                })
              })

            describe("field name 1:name", () => {
              beforeAll(async () => {
                tableOrViewId = await createTableOrView({
                  "1:name": { name: "1:name", type: FieldType.STRING },
                })
                await createRows([{ "1:name": "bar" }, { "1:name": "foo" }])
              })

              it("successfully finds a row", async () => {
                await expectQuery({
                  equal: { "1:1:name": "bar" },
                }).toContainExactly([{ "1:name": "bar" }])
              })

              it("fails to find nonexistent row", async () => {
                await expectQuery({
                  equal: { "1:1:name": "none" },
                }).toFindNothing()
              })
            })

            isSql &&
              describe("related formulas", () => {
                beforeAll(async () => {
                  const arrayTable = await createTable({
                    name: { name: "name", type: FieldType.STRING },
                    array: {
                      name: "array",
                      type: FieldType.ARRAY,
                      constraints: {
                        type: JsonFieldSubType.ARRAY,
                        inclusion: ["option 1", "option 2"],
                      },
                    },
                  })
                  tableOrViewId = await createTableOrView({
                    relationship: {
                      type: FieldType.LINK,
                      relationshipType: RelationshipType.MANY_TO_ONE,
                      name: "relationship",
                      fieldName: "relate",
                      tableId: arrayTable,
                      constraints: {
                        type: "array",
                      },
                    },
                    formula: {
                      type: FieldType.FORMULA,
                      name: "formula",
                      formula: encodeJSBinding(
                        `let array = [];$("relationship").forEach(rel => array = array.concat(rel.array));return array.sort().join(",")`
                      ),
                    },
                  })
                  const arrayRows = await Promise.all([
                    config.api.row.save(arrayTable, {
                      name: "foo",
                      array: ["option 1"],
                    }),
                    config.api.row.save(arrayTable, {
                      name: "bar",
                      array: ["option 2"],
                    }),
                  ])
                  await Promise.all([
                    config.api.row.save(tableOrViewId, {
                      relationship: [arrayRows[0]._id, arrayRows[1]._id],
                    }),
                  ])
                })

                it("formula is correct with relationship arrays", async () => {
                  await expectQuery({}).toContain([
                    { formula: "option 1,option 2" },
                  ])
                })
              })

            describe("user", () => {
              let user1: User
              let user2: User

              beforeAll(async () => {
                user1 = await config.createUser({ _id: `us_${utils.newid()}` })
                user2 = await config.createUser({ _id: `us_${utils.newid()}` })

                tableOrViewId = await createTableOrView({
                  user: {
                    name: "user",
                    type: FieldType.BB_REFERENCE_SINGLE,
                    subtype: BBReferenceFieldSubType.USER,
                  },
                })

                await createRows([
                  { user: user1 },
                  { user: user2 },
                  { user: null },
                ])
              })

              describe("equal", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    equal: { user: user1._id },
                  }).toContainExactly([{ user: { _id: user1._id } }])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({
                    equal: { user: "us_none" },
                  }).toFindNothing()
                })
              })

              describe("notEqual", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    notEqual: { user: user1._id },
                  }).toContainExactly([{ user: { _id: user2._id } }, {}])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({
                    notEqual: { user: "us_none" },
                  }).toContainExactly([
                    { user: { _id: user1._id } },
                    { user: { _id: user2._id } },
                    {},
                  ])
                })
              })

              describe("oneOf", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    oneOf: { user: [user1._id] },
                  }).toContainExactly([{ user: { _id: user1._id } }])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({
                    oneOf: { user: ["us_none"] },
                  }).toFindNothing()
                })
              })

              describe("empty", () => {
                it("finds empty rows", async () => {
                  await expectQuery({ empty: { user: null } }).toContainExactly(
                    [{}]
                  )
                })
              })

              describe("notEmpty", () => {
                it("finds non-empty rows", async () => {
                  await expectQuery({
                    notEmpty: { user: null },
                  }).toContainExactly([
                    { user: { _id: user1._id } },
                    { user: { _id: user2._id } },
                  ])
                })
              })
            })

            describe("multi user", () => {
              let user1: User
              let user2: User

              beforeAll(async () => {
                user1 = await config.createUser({ _id: `us_${utils.newid()}` })
                user2 = await config.createUser({ _id: `us_${utils.newid()}` })

                tableOrViewId = await createTableOrView({
                  users: {
                    name: "users",
                    type: FieldType.BB_REFERENCE,
                    subtype: BBReferenceFieldSubType.USER,
                    constraints: { type: "array" },
                  },
                  number: {
                    name: "number",
                    type: FieldType.NUMBER,
                  },
                })

                await createRows([
                  { number: 1, users: [user1] },
                  { number: 2, users: [user2] },
                  { number: 3, users: [user1, user2] },
                  { number: 4, users: [] },
                ])
              })

              describe("contains", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    contains: { users: [user1._id] },
                  }).toContainExactly([
                    { users: [{ _id: user1._id }] },
                    { users: [{ _id: user1._id }, { _id: user2._id }] },
                  ])
                })

                it("successfully finds a row searching with a string", async () => {
                  await expectQuery({
                    // @ts-expect-error this test specifically goes against the type to
                    // test that we coerce the string to an array.
                    contains: { "1:users": user1._id },
                  }).toContainExactly([
                    { users: [{ _id: user1._id }] },
                    { users: [{ _id: user1._id }, { _id: user2._id }] },
                  ])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({
                    contains: { users: ["us_none"] },
                  }).toFindNothing()
                })
              })

              describe("notContains", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    notContains: { users: [user1._id] },
                  }).toContainExactly([{ users: [{ _id: user2._id }] }, {}])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({
                    notContains: { users: ["us_none"] },
                  }).toContainExactly([
                    { users: [{ _id: user1._id }] },
                    { users: [{ _id: user2._id }] },
                    { users: [{ _id: user1._id }, { _id: user2._id }] },
                    {},
                  ])
                })
              })

              describe("containsAny", () => {
                it("successfully finds rows", async () => {
                  await expectQuery({
                    containsAny: { users: [user1._id, user2._id] },
                  }).toContainExactly([
                    { users: [{ _id: user1._id }] },
                    { users: [{ _id: user2._id }] },
                    { users: [{ _id: user1._id }, { _id: user2._id }] },
                  ])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({
                    containsAny: { users: ["us_none"] },
                  }).toFindNothing()
                })
              })

              describe("multi-column equals", () => {
                it("successfully finds a row", async () => {
                  await expectQuery({
                    equal: { number: 1 },
                    contains: { users: [user1._id] },
                  }).toContainExactly([
                    { users: [{ _id: user1._id }], number: 1 },
                  ])
                })

                it("fails to find nonexistent row", async () => {
                  await expectQuery({
                    equal: { number: 2 },
                    contains: { users: [user1._id] },
                  }).toFindNothing()
                })
              })
            })

            // It also can't work for in-memory searching because the related table name
            // isn't available.
            !isInMemory &&
              describe.each([
                RelationshipType.ONE_TO_MANY,
                RelationshipType.MANY_TO_ONE,
                RelationshipType.MANY_TO_MANY,
              ])("relations (%s)", relationshipType => {
                let productCategoryTable: Table, productCatRows: Row[]

                beforeAll(async () => {
                  const { relatedTable, tableId } =
                    await basicRelationshipTables(relationshipType)
                  tableOrViewId = tableId
                  productCategoryTable = relatedTable

                  productCatRows = await Promise.all([
                    config.api.row.save(productCategoryTable._id!, {
                      name: "foo",
                    }),
                    config.api.row.save(productCategoryTable._id!, {
                      name: "bar",
                    }),
                  ])

                  await Promise.all([
                    config.api.row.save(tableOrViewId, {
                      name: "foo",
                      productCat: [productCatRows[0]._id],
                    }),
                    config.api.row.save(tableOrViewId, {
                      name: "bar",
                      productCat: [productCatRows[1]._id],
                    }),
                    config.api.row.save(tableOrViewId, {
                      name: "baz",
                      productCat: [],
                    }),
                  ])
                })

                it("should be able to filter by relationship using column name", async () => {
                  await expectQuery({
                    equal: { ["productCat.name"]: "foo" },
                  }).toContainExactly([
                    {
                      name: "foo",
                      productCat: [{ _id: productCatRows[0]._id }],
                    },
                  ])
                })

                it("should be able to filter by relationship using table name", async () => {
                  await expectQuery({
                    equal: { [`${productCategoryTable.name}.name`]: "foo" },
                  }).toContainExactly([
                    {
                      name: "foo",
                      productCat: [{ _id: productCatRows[0]._id }],
                    },
                  ])
                })

                it("shouldn't return any relationship for last row", async () => {
                  await expectQuery({
                    equal: { ["name"]: "baz" },
                  }).toContainExactly([{ name: "baz", productCat: undefined }])
                })

                describe("logical filters", () => {
                  const logicalOperators = [
                    LogicalOperator.AND,
                    LogicalOperator.OR,
                  ]

                  describe("$and", () => {
                    it("should allow single conditions", async () => {
                      await expectQuery({
                        $and: {
                          conditions: [
                            {
                              equal: { ["productCat.name"]: "foo" },
                            },
                          ],
                        },
                      }).toContainExactly([
                        {
                          name: "foo",
                          productCat: [{ _id: productCatRows[0]._id }],
                        },
                      ])
                    })

                    it("should allow exclusive conditions", async () => {
                      await expectQuery({
                        $and: {
                          conditions: [
                            {
                              equal: { ["productCat.name"]: "foo" },
                              notEqual: { ["productCat.name"]: "foo" },
                            },
                          ],
                        },
                      }).toContainExactly([])
                    })

                    it.each([logicalOperators])(
                      "should allow nested ands with single conditions (with %s as root)",
                      async rootOperator => {
                        await expectQuery({
                          [rootOperator]: {
                            conditions: [
                              {
                                $and: {
                                  conditions: [
                                    {
                                      equal: { ["productCat.name"]: "foo" },
                                    },
                                  ],
                                },
                              },
                            ],
                          },
                        }).toContainExactly([
                          {
                            name: "foo",
                            productCat: [{ _id: productCatRows[0]._id }],
                          },
                        ])
                      }
                    )

                    it.each([logicalOperators])(
                      "should allow nested ands with exclusive conditions (with %s as root)",
                      async rootOperator => {
                        await expectQuery({
                          [rootOperator]: {
                            conditions: [
                              {
                                $and: {
                                  conditions: [
                                    {
                                      equal: { ["productCat.name"]: "foo" },
                                      notEqual: { ["productCat.name"]: "foo" },
                                    },
                                  ],
                                },
                              },
                            ],
                          },
                        }).toContainExactly([])
                      }
                    )

                    it.each([logicalOperators])(
                      "should allow nested ands with multiple conditions (with %s as root)",
                      async rootOperator => {
                        await expectQuery({
                          [rootOperator]: {
                            conditions: [
                              {
                                $and: {
                                  conditions: [
                                    {
                                      equal: { ["productCat.name"]: "foo" },
                                    },
                                  ],
                                },
                                notEqual: { ["productCat.name"]: "foo" },
                              },
                            ],
                          },
                        }).toContainExactly([])
                      }
                    )
                  })

                  describe("$ors", () => {
                    it("should allow single conditions", async () => {
                      await expectQuery({
                        $or: {
                          conditions: [
                            {
                              equal: { ["productCat.name"]: "foo" },
                            },
                          ],
                        },
                      }).toContainExactly([
                        {
                          name: "foo",
                          productCat: [{ _id: productCatRows[0]._id }],
                        },
                      ])
                    })

                    it("should allow exclusive conditions", async () => {
                      await expectQuery({
                        $or: {
                          conditions: [
                            {
                              equal: { ["productCat.name"]: "foo" },
                              notEqual: { ["productCat.name"]: "foo" },
                            },
                          ],
                        },
                      }).toContainExactly([
                        {
                          name: "foo",
                          productCat: [{ _id: productCatRows[0]._id }],
                        },
                        {
                          name: "bar",
                          productCat: [{ _id: productCatRows[1]._id }],
                        },
                        { name: "baz", productCat: undefined },
                      ])
                    })

                    it.each([logicalOperators])(
                      "should allow nested ors with single conditions (with %s as root)",
                      async rootOperator => {
                        await expectQuery({
                          [rootOperator]: {
                            conditions: [
                              {
                                $or: {
                                  conditions: [
                                    {
                                      equal: { ["productCat.name"]: "foo" },
                                    },
                                  ],
                                },
                              },
                            ],
                          },
                        }).toContainExactly([
                          {
                            name: "foo",
                            productCat: [{ _id: productCatRows[0]._id }],
                          },
                        ])
                      }
                    )

                    it.each([logicalOperators])(
                      "should allow nested ors with exclusive conditions (with %s as root)",
                      async rootOperator => {
                        await expectQuery({
                          [rootOperator]: {
                            conditions: [
                              {
                                $or: {
                                  conditions: [
                                    {
                                      equal: { ["productCat.name"]: "foo" },
                                      notEqual: { ["productCat.name"]: "foo" },
                                    },
                                  ],
                                },
                              },
                            ],
                          },
                        }).toContainExactly([
                          {
                            name: "foo",
                            productCat: [{ _id: productCatRows[0]._id }],
                          },
                          {
                            name: "bar",
                            productCat: [{ _id: productCatRows[1]._id }],
                          },
                          { name: "baz", productCat: undefined },
                        ])
                      }
                    )

                    it("should allow nested ors with multiple conditions", async () => {
                      await expectQuery({
                        $or: {
                          conditions: [
                            {
                              $or: {
                                conditions: [
                                  {
                                    equal: { ["productCat.name"]: "foo" },
                                  },
                                ],
                              },
                              notEqual: { ["productCat.name"]: "foo" },
                            },
                          ],
                        },
                      }).toContainExactly([
                        {
                          name: "foo",
                          productCat: [{ _id: productCatRows[0]._id }],
                        },
                        {
                          name: "bar",
                          productCat: [{ _id: productCatRows[1]._id }],
                        },
                        { name: "baz", productCat: undefined },
                      ])
                    })
                  })
                })
              })

            isSql &&
              describe("relationship - table with spaces", () => {
                let primaryTable: Table, row: Row

                beforeAll(async () => {
                  const { relatedTable, tableId } =
                    await basicRelationshipTables(
                      RelationshipType.ONE_TO_MANY,
                      {
                        tableName: "table with spaces",
                        primaryColumn: "related",
                        otherColumn: "related",
                      }
                    )
                  tableOrViewId = tableId
                  primaryTable = relatedTable

                  row = await config.api.row.save(primaryTable._id!, {
                    name: "foo",
                  })

                  await config.api.row.save(tableOrViewId, {
                    name: "foo",
                    related: [row._id],
                  })
                })

                it("should be able to search by table name with spaces", async () => {
                  await expectQuery({
                    equal: {
                      ["table with spaces.name"]: "foo",
                    },
                  }).toContain([{ name: "foo" }])
                })
              })

            isSql &&
              describe.each([
                RelationshipType.MANY_TO_ONE,
                RelationshipType.MANY_TO_MANY,
              ])("big relations (%s)", relationshipType => {
                beforeAll(async () => {
                  const { relatedTable, tableId } =
                    await basicRelationshipTables(relationshipType)
                  tableOrViewId = tableId
                  const mainRow = await config.api.row.save(tableOrViewId, {
                    name: "foo",
                  })
                  for (let i = 0; i < 11; i++) {
                    await config.api.row.save(relatedTable._id!, {
                      name: i,
                      product: [mainRow._id!],
                    })
                  }
                })

                it("can only pull 10 related rows", async () => {
                  await withCoreEnv(
                    { SQL_MAX_RELATED_ROWS: "10" },
                    async () => {
                      const response = await expectQuery({}).toContain([
                        { name: "foo" },
                      ])
                      expect(response.rows[0].productCat).toBeArrayOfSize(10)
                    }
                  )
                })

                it("can pull max rows when env not set (defaults to 500)", async () => {
                  const response = await expectQuery({}).toContain([
                    { name: "foo" },
                  ])
                  expect(response.rows[0].productCat).toBeArrayOfSize(11)
                })
              })

            isSql &&
              describe("relations to same table", () => {
                let relatedTable: string, relatedRows: Row[]

                beforeAll(async () => {
                  relatedTable = await createTable({
                    name: { name: "name", type: FieldType.STRING },
                  })
                  tableOrViewId = await createTableOrView({
                    name: { name: "name", type: FieldType.STRING },
                    related1: {
                      type: FieldType.LINK,
                      name: "related1",
                      fieldName: "main1",
                      tableId: relatedTable,
                      relationshipType: RelationshipType.MANY_TO_MANY,
                    },
                    related2: {
                      type: FieldType.LINK,
                      name: "related2",
                      fieldName: "main2",
                      tableId: relatedTable,
                      relationshipType: RelationshipType.MANY_TO_MANY,
                    },
                  })
                  relatedRows = await Promise.all([
                    config.api.row.save(relatedTable, { name: "foo" }),
                    config.api.row.save(relatedTable, { name: "bar" }),
                    config.api.row.save(relatedTable, { name: "baz" }),
                    config.api.row.save(relatedTable, { name: "boo" }),
                  ])
                  await Promise.all([
                    config.api.row.save(tableOrViewId, {
                      name: "test",
                      related1: [relatedRows[0]._id!],
                      related2: [relatedRows[1]._id!],
                    }),
                    config.api.row.save(tableOrViewId, {
                      name: "test2",
                      related1: [relatedRows[2]._id!],
                      related2: [relatedRows[3]._id!],
                    }),
                    config.api.row.save(tableOrViewId, {
                      name: "test3",
                      related1: [relatedRows[1]._id],
                      related2: [relatedRows[2]._id!],
                    }),
                  ])
                })

                it("should be able to relate to same table", async () => {
                  await expectSearch({
                    query: {},
                  }).toContainExactly([
                    {
                      name: "test",
                      related1: [{ _id: relatedRows[0]._id }],
                      related2: [{ _id: relatedRows[1]._id }],
                    },
                    {
                      name: "test2",
                      related1: [{ _id: relatedRows[2]._id }],
                      related2: [{ _id: relatedRows[3]._id }],
                    },
                    {
                      name: "test3",
                      related1: [{ _id: relatedRows[1]._id }],
                      related2: [{ _id: relatedRows[2]._id }],
                    },
                  ])
                })

                it("should be able to filter via the first relation field with equal", async () => {
                  await expectSearch({
                    query: {
                      equal: {
                        ["related1.name"]: "baz",
                      },
                    },
                  }).toContainExactly([
                    {
                      name: "test2",
                      related1: [{ _id: relatedRows[2]._id }],
                    },
                  ])
                })

                it("should be able to filter via the second relation field with not equal", async () => {
                  await expectSearch({
                    query: {
                      notEqual: {
                        ["1:related2.name"]: "foo",
                        ["2:related2.name"]: "baz",
                        ["3:related2.name"]: "boo",
                      },
                    },
                  }).toContainExactly([
                    {
                      name: "test",
                    },
                  ])
                })

                it("should be able to filter on both fields", async () => {
                  await expectSearch({
                    query: {
                      notEqual: {
                        ["related1.name"]: "foo",
                        ["related2.name"]: "baz",
                      },
                    },
                  }).toContainExactly([
                    {
                      name: "test2",
                    },
                  ])
                })
              })

            isInternal &&
              describe("no column error backwards compat", () => {
                beforeAll(async () => {
                  tableOrViewId = await createTableOrView({
                    name: {
                      name: "name",
                      type: FieldType.STRING,
                    },
                  })
                })

                it("shouldn't error when column doesn't exist", async () => {
                  await expectSearch({
                    query: {
                      string: {
                        "1:something": "a",
                      },
                    },
                  }).toMatch({ rows: [] })
                })
              })

            describe("row counting", () => {
              beforeAll(async () => {
                tableOrViewId = await createTableOrView({
                  name: {
                    name: "name",
                    type: FieldType.STRING,
                  },
                })
                await createRows([{ name: "a" }, { name: "b" }])
              })

              it("should be able to count rows when option set", async () => {
                await expectSearch({
                  countRows: true,
                  query: {
                    notEmpty: {
                      name: true,
                    },
                  },
                }).toMatch({ totalRows: 2, rows: expect.any(Array) })
              })

              it("shouldn't count rows when option is not set", async () => {
                await expectSearch({
                  countRows: false,
                  query: {
                    notEmpty: {
                      name: true,
                    },
                  },
                }).toNotHaveProperty(["totalRows"])
              })
            })

            describe("Invalid column definitions", () => {
              beforeAll(async () => {
                // need to create an invalid table - means ignoring typescript
                tableOrViewId = await createTableOrView({
                  // @ts-ignore
                  invalid: {
                    type: FieldType.STRING,
                  },
                  name: {
                    name: "name",
                    type: FieldType.STRING,
                  },
                })
                await createRows([
                  { name: "foo", invalid: "id1" },
                  { name: "bar", invalid: "id2" },
                ])
              })

              it("can get rows with all table data", async () => {
                await expectSearch({
                  query: {},
                }).toContain([
                  { name: "foo", invalid: "id1" },
                  { name: "bar", invalid: "id2" },
                ])
              })
            })

            describe.each([
              "data_name_test",
              "name_data_test",
              "name_test_data_",
            ])("special (%s) case", column => {
              beforeAll(async () => {
                tableOrViewId = await createTableOrView({
                  [column]: {
                    name: column,
                    type: FieldType.STRING,
                  },
                })
                await createRows([{ [column]: "a" }, { [column]: "b" }])
              })

              it("should be able to query a column with data_ in it", async () => {
                await expectSearch({
                  query: {
                    equal: {
                      [`1:${column}`]: "a",
                    },
                  },
                }).toContainExactly([{ [column]: "a" }])
              })
            })

            isInternal &&
              describe("sample data", () => {
                beforeAll(async () => {
                  await config.api.application.addSampleData(config.appId!)
                  tableOrViewId = DEFAULT_EMPLOYEE_TABLE_SCHEMA._id!
                  rows = await config.api.row.fetch(tableOrViewId)
                })

                it("should be able to search sample data", async () => {
                  await expectSearch({
                    query: {},
                  }).toContain([
                    {
                      "First Name": "Mandy",
                    },
                  ])
                })
              })

            describe.each([
              {
                low: "2024-07-03T00:00:00.000Z",
                high: "9999-00-00T00:00:00.000Z",
              },
              {
                low: "2024-07-03T00:00:00.000Z",
                high: "9998-00-00T00:00:00.000Z",
              },
              {
                low: "0000-00-00T00:00:00.000Z",
                high: "2024-07-04T00:00:00.000Z",
              },
              {
                low: "0001-00-00T00:00:00.000Z",
                high: "2024-07-04T00:00:00.000Z",
              },
            ])("date special cases", ({ low, high }) => {
              const earlyDate = "2024-07-03T10:00:00.000Z",
                laterDate = "2024-07-03T11:00:00.000Z"
              beforeAll(async () => {
                tableOrViewId = await createTableOrView({
                  date: {
                    name: "date",
                    type: FieldType.DATETIME,
                  },
                })
                await createRows([{ date: earlyDate }, { date: laterDate }])
              })

              it("should be able to handle a date search", async () => {
                await expectSearch({
                  query: {
                    range: {
                      "1:date": { low, high },
                    },
                  },
                }).toContainExactly([{ date: earlyDate }, { date: laterDate }])
              })
            })

            describe.each([
              "名前", // Japanese for "name"
              "Benutzer-ID", // German for "user ID", includes a hyphen
              "numéro", // French for "number", includes an accent
              "år", // Swedish for "year", includes a ring above
              "naïve", // English word borrowed from French, includes an umlaut
              "الاسم", // Arabic for "name"
              "оплата", // Russian for "payment"
              "पता", // Hindi for "address"
              "用戶名", // Chinese for "username"
              "çalışma_zamanı", // Turkish for "runtime", includes an underscore and a cedilla
              "preço", // Portuguese for "price", includes a cedilla
              "사용자명", // Korean for "username"
              "usuario_ñoño", // Spanish, uses an underscore and includes "ñ"
              "файл", // Bulgarian for "file"
              "δεδομένα", // Greek for "data"
              "geändert_am", // German for "modified on", includes an umlaut
              "ব্যবহারকারীর_নাম", // Bengali for "user name", includes an underscore
              "São_Paulo", // Portuguese, includes an underscore and a tilde
              "età", // Italian for "age", includes an accent
              "ชื่อผู้ใช้", // Thai for "username"
            ])("non-ascii column name: %s", name => {
              beforeAll(async () => {
                tableOrViewId = await createTableOrView({
                  [name]: {
                    name,
                    type: FieldType.STRING,
                  },
                })
                await createRows([{ [name]: "a" }, { [name]: "b" }])
              })

              it("should be able to query a column with non-ascii characters", async () => {
                await expectSearch({
                  query: {
                    equal: {
                      [`1:${name}`]: "a",
                    },
                  },
                }).toContainExactly([{ [name]: "a" }])
              })
            })

            // This is currently not supported in external datasources, it produces SQL
            // errors at time of writing. We supported it (potentially by accident) in
            // Lucene, though, so we need to make sure it's supported in SQS as well. We
            // found real cases in production of column names ending in a space.
            isInternal &&
              describe("space at end of column name", () => {
                beforeAll(async () => {
                  tableOrViewId = await createTableOrView({
                    "name ": {
                      name: "name ",
                      type: FieldType.STRING,
                    },
                  })
                  await createRows([{ ["name "]: "foo" }, { ["name "]: "bar" }])
                })

                it("should be able to query a column that ends with a space", async () => {
                  await expectSearch({
                    query: {
                      string: {
                        "name ": "foo",
                      },
                    },
                  }).toContainExactly([{ ["name "]: "foo" }])
                })

                it("should be able to query a column that ends with a space using numeric notation", async () => {
                  await expectSearch({
                    query: {
                      string: {
                        "1:name ": "foo",
                      },
                    },
                  }).toContainExactly([{ ["name "]: "foo" }])
                })
              })

            isInternal &&
              describe("space at start of column name", () => {
                beforeAll(async () => {
                  tableOrViewId = await createTableOrView({
                    " name": {
                      name: " name",
                      type: FieldType.STRING,
                    },
                  })
                  await createRows([{ [" name"]: "foo" }, { [" name"]: "bar" }])
                })

                it("should be able to query a column that starts with a space", async () => {
                  await expectSearch({
                    query: {
                      string: {
                        " name": "foo",
                      },
                    },
                  }).toContainExactly([{ [" name"]: "foo" }])
                })

                it("should be able to query a column that starts with a space using numeric notation", async () => {
                  await expectSearch({
                    query: {
                      string: {
                        "1: name": "foo",
                      },
                    },
                  }).toContainExactly([{ [" name"]: "foo" }])
                })
              })

            isInternal &&
              !isView &&
              describe("duplicate columns", () => {
                beforeAll(async () => {
                  tableOrViewId = await createTableOrView({
                    name: {
                      name: "name",
                      type: FieldType.STRING,
                    },
                  })
                  await context.doInAppContext(config.getAppId(), async () => {
                    const db = context.getAppDB()
                    const tableDoc = await db.get<Table>(tableOrViewId)
                    tableDoc.schema.Name = {
                      name: "Name",
                      type: FieldType.STRING,
                    }
                    try {
                      // remove the SQLite definitions so that they can be rebuilt as part of the search
                      const sqliteDoc = await db.get(SQLITE_DESIGN_DOC_ID)
                      await db.remove(sqliteDoc)
                    } catch (err) {
                      // no-op
                    }
                  })
                  await createRows([{ name: "foo", Name: "bar" }])
                })

                it("should handle invalid duplicate column names", async () => {
                  await expectSearch({
                    query: {},
                  }).toContainExactly([{ name: "foo" }])
                })
              })

            !isInMemory &&
              describe("search by _id", () => {
                let row: Row

                beforeAll(async () => {
                  const toRelateTable = await createTable({
                    name: {
                      name: "name",
                      type: FieldType.STRING,
                    },
                  })
                  tableOrViewId = await createTableOrView({
                    name: {
                      name: "name",
                      type: FieldType.STRING,
                    },
                    rel: {
                      name: "rel",
                      type: FieldType.LINK,
                      relationshipType: RelationshipType.MANY_TO_MANY,
                      tableId: toRelateTable,
                      fieldName: "rel",
                    },
                  })
                  const [row1, row2] = await Promise.all([
                    config.api.row.save(toRelateTable, { name: "tag 1" }),
                    config.api.row.save(toRelateTable, { name: "tag 2" }),
                  ])
                  row = await config.api.row.save(tableOrViewId, {
                    name: "product 1",
                    rel: [row1._id, row2._id],
                  })
                })

                it("can filter by the row ID with limit 1", async () => {
                  await expectSearch({
                    query: {
                      equal: { _id: row._id },
                    },
                    limit: 1,
                  }).toContainExactly([row])
                })

                isInternal &&
                  describe("search by _id for relations", () => {
                    it("can filter by the related _id", async () => {
                      await expectSearch({
                        query: {
                          equal: { "rel._id": row.rel[0]._id },
                        },
                      }).toContainExactly([row])

                      await expectSearch({
                        query: {
                          equal: { "rel._id": row.rel[1]._id },
                        },
                      }).toContainExactly([row])
                    })

                    it("can filter by the related _id and find nothing", async () => {
                      await expectSearch({
                        query: {
                          equal: { "rel._id": "rel_none" },
                        },
                      }).toFindNothing()
                    })
                  })
              })

            !isInternal &&
              describe("search by composite key", () => {
                beforeAll(async () => {
                  const table = await config.api.table.save(
                    tableForDatasource(datasource, {
                      schema: {
                        idColumn1: {
                          name: "idColumn1",
                          type: FieldType.NUMBER,
                        },
                        idColumn2: {
                          name: "idColumn2",
                          type: FieldType.NUMBER,
                        },
                      },
                      primary: ["idColumn1", "idColumn2"],
                    })
                  )
                  tableOrViewId = table._id!
                  await createRows([{ idColumn1: 1, idColumn2: 2 }])
                })

                it("can filter by the row ID with limit 1", async () => {
                  await expectSearch({
                    query: {
                      equal: { _id: generateRowIdField([1, 2]) },
                    },
                    limit: 1,
                  }).toContain([
                    {
                      idColumn1: 1,
                      idColumn2: 2,
                    },
                  ])
                })
              })

            isSql &&
              describe("primaryDisplay", () => {
                beforeAll(async () => {
                  let toRelateTableId = await createTable({
                    name: {
                      name: "name",
                      type: FieldType.STRING,
                    },
                  })
                  tableOrViewId = await createTableOrView({
                    name: {
                      name: "name",
                      type: FieldType.STRING,
                    },
                    link: {
                      name: "link",
                      type: FieldType.LINK,
                      relationshipType: RelationshipType.MANY_TO_ONE,
                      tableId: toRelateTableId,
                      fieldName: "main",
                    },
                  })

                  const toRelateTable = await config.api.table.get(
                    toRelateTableId
                  )
                  await config.api.table.save({
                    ...toRelateTable,
                    primaryDisplay: "name",
                  })
                  const relatedRows = await Promise.all([
                    config.api.row.save(toRelateTable._id!, {
                      name: "related",
                    }),
                  ])
                  await config.api.row.save(tableOrViewId, {
                    name: "test",
                    link: relatedRows.map(row => row._id),
                  })
                })

                it("should be able to query, primary display on related table shouldn't be used", async () => {
                  // this test makes sure that if a relationship has been specified as the primary display on a table
                  // it is ignored and another column is used instead
                  await expectQuery({}).toContain([
                    { name: "test", link: [{ primaryDisplay: "related" }] },
                  ])
                })
              })

            describe("$and", () => {
              beforeAll(async () => {
                tableOrViewId = await createTableOrView({
                  age: { name: "age", type: FieldType.NUMBER },
                  name: { name: "name", type: FieldType.STRING },
                })
                await createRows([
                  { age: 1, name: "Jane" },
                  { age: 10, name: "Jack" },
                  { age: 7, name: "Hanna" },
                  { age: 8, name: "Jan" },
                ])
              })

              it("successfully finds a row for one level condition", async () => {
                await expectQuery({
                  $and: {
                    conditions: [
                      { equal: { age: 10 } },
                      { equal: { name: "Jack" } },
                    ],
                  },
                }).toContainExactly([{ age: 10, name: "Jack" }])
              })

              it("successfully finds a row for one level with multiple conditions", async () => {
                await expectQuery({
                  $and: {
                    conditions: [
                      { equal: { age: 10 } },
                      { equal: { name: "Jack" } },
                    ],
                  },
                }).toContainExactly([{ age: 10, name: "Jack" }])
              })

              it("successfully finds multiple rows for one level with multiple conditions", async () => {
                await expectQuery({
                  $and: {
                    conditions: [
                      { range: { age: { low: 1, high: 9 } } },
                      { string: { name: "Ja" } },
                    ],
                  },
                }).toContainExactly([
                  { age: 1, name: "Jane" },
                  { age: 8, name: "Jan" },
                ])
              })

              it("successfully finds rows for nested filters", async () => {
                await expectQuery({
                  $and: {
                    conditions: [
                      {
                        $and: {
                          conditions: [
                            {
                              range: { age: { low: 1, high: 10 } },
                            },
                            { string: { name: "Ja" } },
                          ],
                        },
                        equal: { name: "Jane" },
                      },
                    ],
                  },
                }).toContainExactly([{ age: 1, name: "Jane" }])
              })

              it("returns nothing when filtering out all data", async () => {
                await expectQuery({
                  $and: {
                    conditions: [
                      { equal: { age: 7 } },
                      { equal: { name: "Jack" } },
                    ],
                  },
                }).toFindNothing()
              })

              !isInMemory &&
                it("validates conditions that are not objects", async () => {
                  await expect(
                    expectQuery({
                      $and: {
                        conditions: [
                          { equal: { age: 10 } },
                          "invalidCondition" as any,
                        ],
                      },
                    }).toFindNothing()
                  ).rejects.toThrow(
                    'Invalid body - "query.$and.conditions[1]" must be of type object'
                  )
                })

              !isInMemory &&
                it("validates $and without conditions", async () => {
                  await expect(
                    expectQuery({
                      $and: {
                        conditions: [
                          { equal: { age: 10 } },
                          {
                            $and: {
                              conditions: undefined as any,
                            },
                          },
                        ],
                      },
                    }).toFindNothing()
                  ).rejects.toThrow(
                    'Invalid body - "query.$and.conditions[1].$and.conditions" is required'
                  )
                })

              // onEmptyFilter cannot be sent to view searches
              !isView &&
                it("returns no rows when onEmptyFilter set to none", async () => {
                  await expectSearch({
                    query: {
                      onEmptyFilter: EmptyFilterOption.RETURN_NONE,
                      $and: {
                        conditions: [{ equal: { name: "" } }],
                      },
                    },
                  }).toFindNothing()
                })

              it("returns all rows when onEmptyFilter set to all", async () => {
                await expectSearch({
                  query: {
                    onEmptyFilter: EmptyFilterOption.RETURN_ALL,
                    $and: {
                      conditions: [{ equal: { name: "" } }],
                    },
                  },
                }).toHaveLength(4)
              })
            })

            describe("$or", () => {
              beforeAll(async () => {
                tableOrViewId = await createTableOrView({
                  age: { name: "age", type: FieldType.NUMBER },
                  name: { name: "name", type: FieldType.STRING },
                })
                await createRows([
                  { age: 1, name: "Jane" },
                  { age: 10, name: "Jack" },
                  { age: 7, name: "Hanna" },
                  { age: 8, name: "Jan" },
                ])
              })

              it("successfully finds a row for one level condition", async () => {
                await expectQuery({
                  $or: {
                    conditions: [
                      { equal: { age: 7 } },
                      { equal: { name: "Jack" } },
                    ],
                  },
                }).toContainExactly([
                  { age: 10, name: "Jack" },
                  { age: 7, name: "Hanna" },
                ])
              })

              it("successfully finds a row for one level with multiple conditions", async () => {
                await expectQuery({
                  $or: {
                    conditions: [
                      { equal: { age: 7 } },
                      { equal: { name: "Jack" } },
                    ],
                  },
                }).toContainExactly([
                  { age: 10, name: "Jack" },
                  { age: 7, name: "Hanna" },
                ])
              })

              it("successfully finds multiple rows for one level with multiple conditions", async () => {
                await expectQuery({
                  $or: {
                    conditions: [
                      { range: { age: { low: 1, high: 9 } } },
                      { string: { name: "Jan" } },
                    ],
                  },
                }).toContainExactly([
                  { age: 1, name: "Jane" },
                  { age: 7, name: "Hanna" },
                  { age: 8, name: "Jan" },
                ])
              })

              it("successfully finds rows for nested filters", async () => {
                await expectQuery({
                  $or: {
                    conditions: [
                      {
                        $or: {
                          conditions: [
                            {
                              range: { age: { low: 1, high: 7 } },
                            },
                            { string: { name: "Jan" } },
                          ],
                        },
                        equal: { name: "Jane" },
                      },
                    ],
                  },
                }).toContainExactly([
                  { age: 1, name: "Jane" },
                  { age: 7, name: "Hanna" },
                  { age: 8, name: "Jan" },
                ])
              })

              it("returns nothing when filtering out all data", async () => {
                await expectQuery({
                  $or: {
                    conditions: [
                      { equal: { age: 6 } },
                      { equal: { name: "John" } },
                    ],
                  },
                }).toFindNothing()
              })

              it("can nest $and under $or filters", async () => {
                await expectQuery({
                  $or: {
                    conditions: [
                      {
                        $and: {
                          conditions: [
                            {
                              range: { age: { low: 1, high: 8 } },
                            },
                            { equal: { name: "Jan" } },
                          ],
                        },
                        equal: { name: "Jane" },
                      },
                    ],
                  },
                }).toContainExactly([
                  { age: 1, name: "Jane" },
                  { age: 8, name: "Jan" },
                ])
              })

              it("can nest $or under $and filters", async () => {
                await expectQuery({
                  $and: {
                    conditions: [
                      {
                        $or: {
                          conditions: [
                            {
                              range: { age: { low: 1, high: 8 } },
                            },
                            { equal: { name: "Jan" } },
                          ],
                        },
                        equal: { name: "Jane" },
                      },
                    ],
                  },
                }).toContainExactly([{ age: 1, name: "Jane" }])
              })

              // onEmptyFilter cannot be sent to view searches
              !isView &&
                it("returns no rows when onEmptyFilter set to none", async () => {
                  await expectSearch({
                    query: {
                      onEmptyFilter: EmptyFilterOption.RETURN_NONE,
                      $or: {
                        conditions: [{ equal: { name: "" } }],
                      },
                    },
                  }).toFindNothing()
                })

              it("returns all rows when onEmptyFilter set to all", async () => {
                await expectSearch({
                  query: {
                    onEmptyFilter: EmptyFilterOption.RETURN_ALL,
                    $or: {
                      conditions: [{ equal: { name: "" } }],
                    },
                  },
                }).toHaveLength(4)
              })
            })

            isSql &&
              describe("max related columns", () => {
                let relatedRows: Row[]

                beforeAll(async () => {
                  const relatedSchema: TableSchema = {}
                  const row: Row = {}
                  for (let i = 0; i < 100; i++) {
                    const name = `column${i}`
                    relatedSchema[name] = { name, type: FieldType.NUMBER }
                    row[name] = i
                  }
                  const relatedTable = await createTable(relatedSchema)
                  tableOrViewId = await createTableOrView({
                    name: { name: "name", type: FieldType.STRING },
                    related1: {
                      type: FieldType.LINK,
                      name: "related1",
                      fieldName: "main1",
                      tableId: relatedTable,
                      relationshipType: RelationshipType.MANY_TO_MANY,
                    },
                  })
                  relatedRows = await Promise.all([
                    config.api.row.save(relatedTable, row),
                  ])
                  await config.api.row.save(tableOrViewId, {
                    name: "foo",
                    related1: [relatedRows[0]._id],
                  })
                })

                it("retrieve the row with relationships", async () => {
                  await expectQuery({}).toContainExactly([
                    {
                      name: "foo",
                      related1: [{ _id: relatedRows[0]._id }],
                    },
                  ])
                })
              })

            !isInternal &&
              describe("SQL injection", () => {
                const badStrings = [
                  "1; DROP TABLE %table_name%;",
                  "1; DELETE FROM %table_name%;",
                  "1; UPDATE %table_name% SET name = 'foo';",
                  "1; INSERT INTO %table_name% (name) VALUES ('foo');",
                  "' OR '1'='1' --",
                  "'; DROP TABLE %table_name%; --",
                  "' OR 1=1 --",
                  "' UNION SELECT null, null, null; --",
                  "' AND (SELECT COUNT(*) FROM %table_name%) > 0 --",
                  "\"; EXEC xp_cmdshell('dir'); --",
                  "\"' OR 'a'='a",
                  "OR 1=1;",
                  "'; SHUTDOWN --",
                ]

                describe.each(badStrings)(
                  "bad string: %s",
                  badStringTemplate => {
                    // The SQL that knex generates when you try to use a double quote in a
                    // field name is always invalid and never works, so we skip it for these
                    // tests.
                    const skipFieldNameCheck =
                      isOracle && badStringTemplate.includes('"')

                    !skipFieldNameCheck &&
                      it("should not allow SQL injection as a field name", async () => {
                        const tableOrViewId = await createTableOrView()
                        const table = await getTable(tableOrViewId)
                        const badString = badStringTemplate.replace(
                          /%table_name%/g,
                          table.name
                        )

                        await config.api.table.save({
                          ...table,
                          schema: {
                            ...table.schema,
                            [badString]: {
                              name: badString,
                              type: FieldType.STRING,
                            },
                          },
                        })

                        if (docIds.isViewId(tableOrViewId)) {
                          const view = await config.api.viewV2.get(
                            tableOrViewId
                          )
                          await config.api.viewV2.update({
                            ...view,
                            schema: {
                              [badString]: { visible: true },
                            },
                          })
                        }

                        await config.api.row.save(tableOrViewId, {
                          [badString]: "foo",
                        })

                        await assertTableExists(table)
                        await assertTableNumRows(table, 1)

                        const { rows } = await config.api.row.search(
                          tableOrViewId,
                          { query: {} },
                          { status: 200 }
                        )

                        expect(rows).toHaveLength(1)

                        await assertTableExists(table)
                        await assertTableNumRows(table, 1)
                      })

                    it("should not allow SQL injection as a field value", async () => {
                      const tableOrViewId = await createTableOrView({
                        foo: {
                          name: "foo",
                          type: FieldType.STRING,
                        },
                      })
                      const table = await getTable(tableOrViewId)
                      const badString = badStringTemplate.replace(
                        /%table_name%/g,
                        table.name
                      )

                      await config.api.row.save(tableOrViewId, { foo: "foo" })

                      await assertTableExists(table)
                      await assertTableNumRows(table, 1)

                      const { rows } = await config.api.row.search(
                        tableOrViewId,
                        { query: { equal: { foo: badString } } },
                        { status: 200 }
                      )

                      expect(rows).toBeEmpty()
                      await assertTableExists(table)
                      await assertTableNumRows(table, 1)
                    })
                  }
                )
              })
          }
        )
      })
    }
  )
}