firebase / firebase-tools

The Firebase Command Line Tools
MIT License
4.01k stars 929 forks source link

[firestore-emulator: datastore-mode] Unexpected response from query filter on nested property in array #7677

Open boris-hocde opened 4 days ago

boris-hocde commented 4 days ago

[REQUIRED] Environment info

gcloud: v493.0.0

Platform: Ubuntu

[REQUIRED] Test case

package.json

{
  "version": "1.0.0",
  "description": "",
  "main": "lib/index.js",
  "scripts": {
    "build": "tsc",
    "start": "DATASTORE_EMULATOR_HOST=127.0.0.1:8080 node lib/index.js",
    "start:emulator:old": "gcloud --project=dummy-project-id beta emulators datastore start --use-firestore-in-datastore-mode --host-port=127.0.0.1:8080",
    "start:emulator:new": "gcloud emulators firestore start --database-mode=datastore-mode --host-port=127.0.0.1:8080"
  },
  "devDependencies": {
    "@google-cloud/datastore": "^9.1.0",
    "typescript": "^5.4.3"
  }
}

src/index.ts

import {and, Datastore, PropertyFilter} from "@google-cloud/datastore";

const datastore = new Datastore({
  projectId: "dummy-project-id",
});

const KIND_USER = 'User'

type User = {
  email: string
  fields : {
    key: string,
    value: string,
  }[]
}

export async function getUsersByName(name: string) {
  const query = datastore.createQuery(KIND_USER).filter(and([
    new PropertyFilter('fields.key','=','name'),
    new PropertyFilter('fields.value','=',name),
  ]));
  const [entities] = await datastore.runQuery(query);
  return entities
}

export async function addUser(data: User) {
  const key = datastore.key(KIND_USER);
  const entity = {
    key: key,
    data,
  };
  await datastore.save(entity);
}

async function main() {
  await addUser({email: "foo@example.com", fields: [{key: "name", value: "Awesome Name"}]})

  const users = await getUsersByName("Awesome Name");
  console.log(users);
}

main();

tsconfig.json

{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "outDir": "lib"
  },
  "include": ["src"],
  "exclude": []
}

[REQUIRED] Steps to reproduce

Datastore Emulator (Old)

npm install
npm run start:emulator:old
$ npm run build && npm run start

> build
> tsc

> start
> DATASTORE_EMULATOR_HOST=127.0.0.1:8080 node lib/index.js

[
  {
    fields: [ [Object] ],
    email: 'foo@example.com',
    [Symbol(KEY)]: Key {
      namespace: undefined,
      id: '1',
      kind: 'User',
      path: [Getter]
    }
  },
]

Firestore Emulator (New, Preview)

npm install
npm run start:emulator:new
$ npm run build && npm run start

> build
> tsc

> start
> DATASTORE_EMULATOR_HOST=127.0.0.1:8080 node lib/index.js

[]

[REQUIRED] Expected behavior

[
  {
    fields: [ [Object] ],
    email: 'foo@example.com',
    [Symbol(KEY)]: Key {
      namespace: undefined,
      id: '1',
      kind: 'User',
      path: [Getter]
    }
  },
]

[REQUIRED] Actual behavior

The result filtered by fields.name and fields.value should include the user, but not when using the new emulator.

[]

This issue slightly differs from https://github.com/firebase/firebase-tools/issues/6999, as the fields property is an array here.

aalej commented 2 days ago

Hey @boris-hocde, thanks for creating a detailed report! I'm able to reproduce the issue using the code snippets and steps provided. I’ll mark this as reproducible and raise this to our engineering team.