diff --git a/melos.yaml b/melos.yaml index 78ba09d39ad5..f7cc3c94b6e9 100644 --- a/melos.yaml +++ b/melos.yaml @@ -266,6 +266,7 @@ scripts: --ignore "**/*.yaml" \ --ignore "**/*.xml" \ --ignore "**/*.g.dart" \ + --ignore "**/*.mocks.dart" \ --ignore "**/*.sh" \ --ignore "**/*.html" \ --ignore "**/*.js" \ @@ -302,6 +303,7 @@ scripts: --ignore "**/*.yaml" \ --ignore "**/*.xml" \ --ignore "**/*.g.dart" \ + --ignore "**/*.mocks.dart" \ --ignore "**/*.sh" \ --ignore "**/*.html" \ --ignore "**/*.js" \ diff --git a/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/main/implicit.gql b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/main/implicit.gql new file mode 100644 index 000000000000..d7fbe1ff8d51 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/main/implicit.gql @@ -0,0 +1,28 @@ +extend type DirectedBy { + """ + ✨ Implicit foreign key field based on `DirectedBy`.`movie`. It must match the value of `Movie`.`id`. See `@ref` for how to customize it. + """ + movieId: UUID! @fdc_generated(from: "DirectedBy.movie", purpose: IMPLICIT_REF_FIELD) + """ + ✨ Implicit foreign key field based on `DirectedBy`.`directedby`. It must match the value of `Person`.`id`. See `@ref` for how to customize it. + """ + directedbyId: UUID! @fdc_generated(from: "DirectedBy.directedby", purpose: IMPLICIT_REF_FIELD) +} +extend type Movie { + """ + ✨ Implicit primary key field. It's a UUID column default to a generated new value. See `@table` for how to customize it. + """ + id: UUID! @default(expr: "uuidV4()") @fdc_generated(from: "Movie", purpose: IMPLICIT_KEY_FIELD) +} +extend type Person { + """ + ✨ Implicit primary key field. It's a UUID column default to a generated new value. See `@table` for how to customize it. + """ + id: UUID! @default(expr: "uuidV4()") @fdc_generated(from: "Person", purpose: IMPLICIT_KEY_FIELD) +} +extend type Thing { + """ + ✨ Implicit primary key field. It's a UUID column default to a generated new value. See `@table` for how to customize it. + """ + id: UUID! @default(expr: "uuidV4()") @fdc_generated(from: "Thing", purpose: IMPLICIT_KEY_FIELD) +} diff --git a/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/main/input.gql b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/main/input.gql new file mode 100644 index 000000000000..483808bee877 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/main/input.gql @@ -0,0 +1,632 @@ +""" +✨ `DirectedBy_KeyOutput` returns the primary key fields of table type `DirectedBy`. + +It has the same format as `DirectedBy_Key`, but is only used as mutation return value. +""" +scalar DirectedBy_KeyOutput +""" +✨ `Movie_KeyOutput` returns the primary key fields of table type `Movie`. + +It has the same format as `Movie_Key`, but is only used as mutation return value. +""" +scalar Movie_KeyOutput +""" +✨ `Person_KeyOutput` returns the primary key fields of table type `Person`. + +It has the same format as `Person_Key`, but is only used as mutation return value. +""" +scalar Person_KeyOutput +""" +✨ `Thing_KeyOutput` returns the primary key fields of table type `Thing`. + +It has the same format as `Thing_Key`, but is only used as mutation return value. +""" +scalar Thing_KeyOutput +""" +✨ `TimestampHolder_KeyOutput` returns the primary key fields of table type `TimestampHolder`. + +It has the same format as `TimestampHolder_Key`, but is only used as mutation return value. +""" +scalar TimestampHolder_KeyOutput +""" +✨ Generated data input type for table 'DirectedBy'. It includes all necessary fields for creating or upserting rows into table. +""" +input DirectedBy_Data { + """ + ✨ Generated from Field `DirectedBy`.`movieId` of type `UUID!` + """ + movieId: UUID + """ + ✨ `_expr` server value variant of `movieId` (✨ Generated from Field `DirectedBy`.`movieId` of type `UUID!`) + """ + movieId_expr: UUID_Expr + """ + ✨ Generated from Field `DirectedBy`.`directedbyId` of type `UUID!` + """ + directedbyId: UUID + """ + ✨ `_expr` server value variant of `directedbyId` (✨ Generated from Field `DirectedBy`.`directedbyId` of type `UUID!`) + """ + directedbyId_expr: UUID_Expr + """ + ✨ Generated from Field `DirectedBy`.`directedby` of type `Person!` + """ + directedby: Person_Key + """ + ✨ Generated from Field `DirectedBy`.`movie` of type `Movie!` + """ + movie: Movie_Key +} +""" +✨ Generated filter input type for table 'DirectedBy'. This input allows filtering objects using various conditions. Use `_or`, `_and`, and `_not` to compose complex filters. +""" +input DirectedBy_Filter { + """ + Apply multiple filter conditions using `AND` logic. + """ + _and: [DirectedBy_Filter!] + """ + Negate the result of the provided filter condition. + """ + _not: DirectedBy_Filter + """ + Apply multiple filter conditions using `OR` logic. + """ + _or: [DirectedBy_Filter!] + """ + ✨ Generated from Field `DirectedBy`.`movieId` of type `UUID!` + """ + movieId: UUID_Filter + """ + ✨ Generated from Field `DirectedBy`.`directedbyId` of type `UUID!` + """ + directedbyId: UUID_Filter + """ + ✨ Generated from Field `DirectedBy`.`directedby` of type `Person!` + """ + directedby: Person_Filter + """ + ✨ Generated from Field `DirectedBy`.`movie` of type `Movie!` + """ + movie: Movie_Filter +} +""" +✨ Generated first-row input type for table 'DirectedBy'. This input selects the first row matching the filter criteria, ordered according to the specified conditions. +""" +input DirectedBy_FirstRow { + """ + Order the result by the specified fields. + """ + orderBy: [DirectedBy_Order!] + """ + Filters rows based on the specified conditions. + """ + where: DirectedBy_Filter +} +""" +✨ Generated key input type for table 'DirectedBy'. It represents the primary key fields used to uniquely identify a row in the table. +""" +input DirectedBy_Key { + """ + ✨ Generated from Field `DirectedBy`.`movieId` of type `UUID!` + """ + movieId: UUID + """ + ✨ `_expr` server value variant of `movieId` (✨ Generated from Field `DirectedBy`.`movieId` of type `UUID!`) + """ + movieId_expr: UUID_Expr + """ + ✨ Generated from Field `DirectedBy`.`directedbyId` of type `UUID!` + """ + directedbyId: UUID + """ + ✨ `_expr` server value variant of `directedbyId` (✨ Generated from Field `DirectedBy`.`directedbyId` of type `UUID!`) + """ + directedbyId_expr: UUID_Expr +} +""" +✨ Generated list filter input type for table 'DirectedBy'. This input applies filtering logic based on the count or existence of related objects that matches certain criteria. +""" +input DirectedBy_ListFilter { + """ + The desired number of objects that match the condition (defaults to at least one). + """ + count: Int_Filter = {gt:0} + """ + Condition of the related objects to filter for. + """ + exist: DirectedBy_Filter +} +""" +✨ Generated order input type for table 'DirectedBy'. This input defines the sorting order of rows in query results based on one or more fields. +""" +input DirectedBy_Order { + """ + ✨ Generated from Field `DirectedBy`.`movieId` of type `UUID!` + """ + movieId: OrderDirection + """ + ✨ Generated from Field `DirectedBy`.`directedbyId` of type `UUID!` + """ + directedbyId: OrderDirection + """ + ✨ Generated from Field `DirectedBy`.`directedby` of type `Person!` + """ + directedby: Person_Order + """ + ✨ Generated from Field `DirectedBy`.`movie` of type `Movie!` + """ + movie: Movie_Order +} +""" +✨ Generated data input type for table 'Movie'. It includes all necessary fields for creating or upserting rows into table. +""" +input Movie_Data { + """ + ✨ Generated from Field `Movie`.`id` of type `UUID!` + """ + id: UUID + """ + ✨ `_expr` server value variant of `id` (✨ Generated from Field `Movie`.`id` of type `UUID!`) + """ + id_expr: UUID_Expr + """ + ✨ Generated from Field `Movie`.`description` of type `String` + """ + description: String + """ + ✨ `_expr` server value variant of `description` (✨ Generated from Field `Movie`.`description` of type `String`) + """ + description_expr: String_Expr + """ + ✨ Generated from Field `Movie`.`genre` of type `String!` + """ + genre: String + """ + ✨ `_expr` server value variant of `genre` (✨ Generated from Field `Movie`.`genre` of type `String!`) + """ + genre_expr: String_Expr + """ + ✨ Generated from Field `Movie`.`rating` of type `Float` + """ + rating: Float + """ + ✨ Generated from Field `Movie`.`releaseYear` of type `Int` + """ + releaseYear: Int + """ + ✨ Generated from Field `Movie`.`title` of type `String!` + """ + title: String + """ + ✨ `_expr` server value variant of `title` (✨ Generated from Field `Movie`.`title` of type `String!`) + """ + title_expr: String_Expr +} +""" +✨ Generated filter input type for table 'Movie'. This input allows filtering objects using various conditions. Use `_or`, `_and`, and `_not` to compose complex filters. +""" +input Movie_Filter { + """ + Apply multiple filter conditions using `AND` logic. + """ + _and: [Movie_Filter!] + """ + Negate the result of the provided filter condition. + """ + _not: Movie_Filter + """ + Apply multiple filter conditions using `OR` logic. + """ + _or: [Movie_Filter!] + """ + ✨ Generated from Field `Movie`.`id` of type `UUID!` + """ + id: UUID_Filter + """ + ✨ Generated from Field `Movie`.`description` of type `String` + """ + description: String_Filter + """ + ✨ Generated from Field `Movie`.`genre` of type `String!` + """ + genre: String_Filter + """ + ✨ Generated from Field `Movie`.`rating` of type `Float` + """ + rating: Float_Filter + """ + ✨ Generated from Field `Movie`.`releaseYear` of type `Int` + """ + releaseYear: Int_Filter + """ + ✨ Generated from Field `Movie`.`title` of type `String!` + """ + title: String_Filter + """ + ✨ Generated from Field `Movie`.`directedBies_on_movie` of type `[DirectedBy!]!` + """ + directedBies_on_movie: DirectedBy_ListFilter + """ + ✨ Generated from Field `Movie`.`people_via_DirectedBy` of type `[Person!]!` + """ + people_via_DirectedBy: Person_ListFilter +} +""" +✨ Generated first-row input type for table 'Movie'. This input selects the first row matching the filter criteria, ordered according to the specified conditions. +""" +input Movie_FirstRow { + """ + Order the result by the specified fields. + """ + orderBy: [Movie_Order!] + """ + Filters rows based on the specified conditions. + """ + where: Movie_Filter +} +""" +✨ Generated key input type for table 'Movie'. It represents the primary key fields used to uniquely identify a row in the table. +""" +input Movie_Key { + """ + ✨ Generated from Field `Movie`.`id` of type `UUID!` + """ + id: UUID + """ + ✨ `_expr` server value variant of `id` (✨ Generated from Field `Movie`.`id` of type `UUID!`) + """ + id_expr: UUID_Expr +} +""" +✨ Generated list filter input type for table 'Movie'. This input applies filtering logic based on the count or existence of related objects that matches certain criteria. +""" +input Movie_ListFilter { + """ + The desired number of objects that match the condition (defaults to at least one). + """ + count: Int_Filter = {gt:0} + """ + Condition of the related objects to filter for. + """ + exist: Movie_Filter +} +""" +✨ Generated order input type for table 'Movie'. This input defines the sorting order of rows in query results based on one or more fields. +""" +input Movie_Order { + """ + ✨ Generated from Field `Movie`.`id` of type `UUID!` + """ + id: OrderDirection + """ + ✨ Generated from Field `Movie`.`description` of type `String` + """ + description: OrderDirection + """ + ✨ Generated from Field `Movie`.`genre` of type `String!` + """ + genre: OrderDirection + """ + ✨ Generated from Field `Movie`.`rating` of type `Float` + """ + rating: OrderDirection + """ + ✨ Generated from Field `Movie`.`releaseYear` of type `Int` + """ + releaseYear: OrderDirection + """ + ✨ Generated from Field `Movie`.`title` of type `String!` + """ + title: OrderDirection +} +""" +✨ Generated data input type for table 'Person'. It includes all necessary fields for creating or upserting rows into table. +""" +input Person_Data { + """ + ✨ Generated from Field `Person`.`id` of type `UUID!` + """ + id: UUID + """ + ✨ `_expr` server value variant of `id` (✨ Generated from Field `Person`.`id` of type `UUID!`) + """ + id_expr: UUID_Expr + """ + ✨ Generated from Field `Person`.`name` of type `String!` + """ + name: String + """ + ✨ `_expr` server value variant of `name` (✨ Generated from Field `Person`.`name` of type `String!`) + """ + name_expr: String_Expr +} +""" +✨ Generated filter input type for table 'Person'. This input allows filtering objects using various conditions. Use `_or`, `_and`, and `_not` to compose complex filters. +""" +input Person_Filter { + """ + Apply multiple filter conditions using `AND` logic. + """ + _and: [Person_Filter!] + """ + Negate the result of the provided filter condition. + """ + _not: Person_Filter + """ + Apply multiple filter conditions using `OR` logic. + """ + _or: [Person_Filter!] + """ + ✨ Generated from Field `Person`.`id` of type `UUID!` + """ + id: UUID_Filter + """ + ✨ Generated from Field `Person`.`name` of type `String!` + """ + name: String_Filter + """ + ✨ Generated from Field `Person`.`directedBies_on_directedby` of type `[DirectedBy!]!` + """ + directedBies_on_directedby: DirectedBy_ListFilter + """ + ✨ Generated from Field `Person`.`movies_via_DirectedBy` of type `[Movie!]!` + """ + movies_via_DirectedBy: Movie_ListFilter +} +""" +✨ Generated first-row input type for table 'Person'. This input selects the first row matching the filter criteria, ordered according to the specified conditions. +""" +input Person_FirstRow { + """ + Order the result by the specified fields. + """ + orderBy: [Person_Order!] + """ + Filters rows based on the specified conditions. + """ + where: Person_Filter +} +""" +✨ Generated key input type for table 'Person'. It represents the primary key fields used to uniquely identify a row in the table. +""" +input Person_Key { + """ + ✨ Generated from Field `Person`.`id` of type `UUID!` + """ + id: UUID + """ + ✨ `_expr` server value variant of `id` (✨ Generated from Field `Person`.`id` of type `UUID!`) + """ + id_expr: UUID_Expr +} +""" +✨ Generated list filter input type for table 'Person'. This input applies filtering logic based on the count or existence of related objects that matches certain criteria. +""" +input Person_ListFilter { + """ + The desired number of objects that match the condition (defaults to at least one). + """ + count: Int_Filter = {gt:0} + """ + Condition of the related objects to filter for. + """ + exist: Person_Filter +} +""" +✨ Generated order input type for table 'Person'. This input defines the sorting order of rows in query results based on one or more fields. +""" +input Person_Order { + """ + ✨ Generated from Field `Person`.`id` of type `UUID!` + """ + id: OrderDirection + """ + ✨ Generated from Field `Person`.`name` of type `String!` + """ + name: OrderDirection +} +""" +✨ Generated data input type for table 'Thing'. It includes all necessary fields for creating or upserting rows into table. +""" +input Thing_Data { + """ + ✨ Generated from Field `Thing`.`id` of type `UUID!` + """ + id: UUID + """ + ✨ `_expr` server value variant of `id` (✨ Generated from Field `Thing`.`id` of type `UUID!`) + """ + id_expr: UUID_Expr + """ + ✨ Generated from Field `Thing`.`title` of type `Any!` + """ + title: Any +} +""" +✨ Generated filter input type for table 'Thing'. This input allows filtering objects using various conditions. Use `_or`, `_and`, and `_not` to compose complex filters. +""" +input Thing_Filter { + """ + Apply multiple filter conditions using `AND` logic. + """ + _and: [Thing_Filter!] + """ + Negate the result of the provided filter condition. + """ + _not: Thing_Filter + """ + Apply multiple filter conditions using `OR` logic. + """ + _or: [Thing_Filter!] + """ + ✨ Generated from Field `Thing`.`id` of type `UUID!` + """ + id: UUID_Filter + """ + ✨ Generated from Field `Thing`.`title` of type `Any!` + """ + title: Any_Filter +} +""" +✨ Generated first-row input type for table 'Thing'. This input selects the first row matching the filter criteria, ordered according to the specified conditions. +""" +input Thing_FirstRow { + """ + Order the result by the specified fields. + """ + orderBy: [Thing_Order!] + """ + Filters rows based on the specified conditions. + """ + where: Thing_Filter +} +""" +✨ Generated key input type for table 'Thing'. It represents the primary key fields used to uniquely identify a row in the table. +""" +input Thing_Key { + """ + ✨ Generated from Field `Thing`.`id` of type `UUID!` + """ + id: UUID + """ + ✨ `_expr` server value variant of `id` (✨ Generated from Field `Thing`.`id` of type `UUID!`) + """ + id_expr: UUID_Expr +} +""" +✨ Generated list filter input type for table 'Thing'. This input applies filtering logic based on the count or existence of related objects that matches certain criteria. +""" +input Thing_ListFilter { + """ + The desired number of objects that match the condition (defaults to at least one). + """ + count: Int_Filter = {gt:0} + """ + Condition of the related objects to filter for. + """ + exist: Thing_Filter +} +""" +✨ Generated order input type for table 'Thing'. This input defines the sorting order of rows in query results based on one or more fields. +""" +input Thing_Order { + """ + ✨ Generated from Field `Thing`.`id` of type `UUID!` + """ + id: OrderDirection + """ + ✨ Generated from Field `Thing`.`title` of type `Any!` + """ + title: OrderDirection +} +""" +✨ Generated data input type for table 'TimestampHolder'. It includes all necessary fields for creating or upserting rows into table. +""" +input TimestampHolder_Data { + """ + ✨ Generated from Field `TimestampHolder`.`timestamp` of type `Timestamp!` + """ + timestamp: Timestamp + """ + ✨ `_expr` server value variant of `timestamp` (✨ Generated from Field `TimestampHolder`.`timestamp` of type `Timestamp!`) + """ + timestamp_expr: Timestamp_Expr + """ + ✨ `_time` server value variant of `timestamp` (✨ Generated from Field `TimestampHolder`.`timestamp` of type `Timestamp!`) + """ + timestamp_time: Timestamp_Relative + """ + ✨ Generated from Field `TimestampHolder`.`date` of type `Date` + """ + date: Date + """ + ✨ `_date` server value variant of `date` (✨ Generated from Field `TimestampHolder`.`date` of type `Date`) + """ + date_date: Date_Relative + """ + ✨ `_expr` server value variant of `date` (✨ Generated from Field `TimestampHolder`.`date` of type `Date`) + """ + date_expr: Date_Expr +} +""" +✨ Generated filter input type for table 'TimestampHolder'. This input allows filtering objects using various conditions. Use `_or`, `_and`, and `_not` to compose complex filters. +""" +input TimestampHolder_Filter { + """ + Apply multiple filter conditions using `AND` logic. + """ + _and: [TimestampHolder_Filter!] + """ + Negate the result of the provided filter condition. + """ + _not: TimestampHolder_Filter + """ + Apply multiple filter conditions using `OR` logic. + """ + _or: [TimestampHolder_Filter!] + """ + ✨ Generated from Field `TimestampHolder`.`timestamp` of type `Timestamp!` + """ + timestamp: Timestamp_Filter + """ + ✨ Generated from Field `TimestampHolder`.`date` of type `Date` + """ + date: Date_Filter +} +""" +✨ Generated first-row input type for table 'TimestampHolder'. This input selects the first row matching the filter criteria, ordered according to the specified conditions. +""" +input TimestampHolder_FirstRow { + """ + Order the result by the specified fields. + """ + orderBy: [TimestampHolder_Order!] + """ + Filters rows based on the specified conditions. + """ + where: TimestampHolder_Filter +} +""" +✨ Generated key input type for table 'TimestampHolder'. It represents the primary key fields used to uniquely identify a row in the table. +""" +input TimestampHolder_Key { + """ + ✨ Generated from Field `TimestampHolder`.`timestamp` of type `Timestamp!` + """ + timestamp: Timestamp + """ + ✨ `_expr` server value variant of `timestamp` (✨ Generated from Field `TimestampHolder`.`timestamp` of type `Timestamp!`) + """ + timestamp_expr: Timestamp_Expr + """ + ✨ `_time` server value variant of `timestamp` (✨ Generated from Field `TimestampHolder`.`timestamp` of type `Timestamp!`) + """ + timestamp_time: Timestamp_Relative +} +""" +✨ Generated list filter input type for table 'TimestampHolder'. This input applies filtering logic based on the count or existence of related objects that matches certain criteria. +""" +input TimestampHolder_ListFilter { + """ + The desired number of objects that match the condition (defaults to at least one). + """ + count: Int_Filter = {gt:0} + """ + Condition of the related objects to filter for. + """ + exist: TimestampHolder_Filter +} +""" +✨ Generated order input type for table 'TimestampHolder'. This input defines the sorting order of rows in query results based on one or more fields. +""" +input TimestampHolder_Order { + """ + ✨ Generated from Field `TimestampHolder`.`timestamp` of type `Timestamp!` + """ + timestamp: OrderDirection + """ + ✨ Generated from Field `TimestampHolder`.`date` of type `Date` + """ + date: OrderDirection +} diff --git a/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/main/mutation.gql b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/main/mutation.gql new file mode 100644 index 000000000000..947c23afa779 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/main/mutation.gql @@ -0,0 +1,497 @@ +extend type Mutation { + """ + ✨ Insert a single `DirectedBy` into the table. Columns not specified in `data` will receive defaults (e.g. `null`). + """ + directedBy_insert( + """ + Data object to insert into the table. + """ + data: DirectedBy_Data! + ): DirectedBy_KeyOutput! @fdc_generated(from: "DirectedBy", purpose: INSERT_SINGLE) + """ + ✨ Insert a single `Movie` into the table. Columns not specified in `data` will receive defaults (e.g. `null`). + """ + movie_insert( + """ + Data object to insert into the table. + """ + data: Movie_Data! + ): Movie_KeyOutput! @fdc_generated(from: "Movie", purpose: INSERT_SINGLE) + """ + ✨ Insert a single `Person` into the table. Columns not specified in `data` will receive defaults (e.g. `null`). + """ + person_insert( + """ + Data object to insert into the table. + """ + data: Person_Data! + ): Person_KeyOutput! @fdc_generated(from: "Person", purpose: INSERT_SINGLE) + """ + ✨ Insert a single `Thing` into the table. Columns not specified in `data` will receive defaults (e.g. `null`). + """ + thing_insert( + """ + Data object to insert into the table. + """ + data: Thing_Data! + ): Thing_KeyOutput! @fdc_generated(from: "Thing", purpose: INSERT_SINGLE) + """ + ✨ Insert a single `TimestampHolder` into the table. Columns not specified in `data` will receive defaults (e.g. `null`). + """ + timestampHolder_insert( + """ + Data object to insert into the table. + """ + data: TimestampHolder_Data! + ): TimestampHolder_KeyOutput! @fdc_generated(from: "TimestampHolder", purpose: INSERT_SINGLE) + """ + ✨ Insert `DirectedBy` objects into the table. Columns not specified in `data` will receive defaults (e.g. `null`). + """ + directedBy_insertMany( + """ + List of data objects to insert into the table. + """ + data: [DirectedBy_Data!]! + ): [DirectedBy_KeyOutput!]! @fdc_generated(from: "DirectedBy", purpose: INSERT_MULTIPLE) + """ + ✨ Insert `Movie` objects into the table. Columns not specified in `data` will receive defaults (e.g. `null`). + """ + movie_insertMany( + """ + List of data objects to insert into the table. + """ + data: [Movie_Data!]! + ): [Movie_KeyOutput!]! @fdc_generated(from: "Movie", purpose: INSERT_MULTIPLE) + """ + ✨ Insert `Person` objects into the table. Columns not specified in `data` will receive defaults (e.g. `null`). + """ + person_insertMany( + """ + List of data objects to insert into the table. + """ + data: [Person_Data!]! + ): [Person_KeyOutput!]! @fdc_generated(from: "Person", purpose: INSERT_MULTIPLE) + """ + ✨ Insert `Thing` objects into the table. Columns not specified in `data` will receive defaults (e.g. `null`). + """ + thing_insertMany( + """ + List of data objects to insert into the table. + """ + data: [Thing_Data!]! + ): [Thing_KeyOutput!]! @fdc_generated(from: "Thing", purpose: INSERT_MULTIPLE) + """ + ✨ Insert `TimestampHolder` objects into the table. Columns not specified in `data` will receive defaults (e.g. `null`). + """ + timestampHolder_insertMany( + """ + List of data objects to insert into the table. + """ + data: [TimestampHolder_Data!]! + ): [TimestampHolder_KeyOutput!]! @fdc_generated(from: "TimestampHolder", purpose: INSERT_MULTIPLE) + """ + ✨ Insert or update a single `DirectedBy` into the table, based on the primary key. Returns the key of the newly inserted `DirectedBy`. + """ + directedBy_upsert( + """ + Data object to insert or update if it already exists. + """ + data: DirectedBy_Data! + ): DirectedBy_KeyOutput! @fdc_generated(from: "DirectedBy", purpose: UPSERT_SINGLE) + """ + ✨ Insert or update a single `Movie` into the table, based on the primary key. Returns the key of the newly inserted `Movie`. + """ + movie_upsert( + """ + Data object to insert or update if it already exists. + """ + data: Movie_Data! + ): Movie_KeyOutput! @fdc_generated(from: "Movie", purpose: UPSERT_SINGLE) + """ + ✨ Insert or update a single `Person` into the table, based on the primary key. Returns the key of the newly inserted `Person`. + """ + person_upsert( + """ + Data object to insert or update if it already exists. + """ + data: Person_Data! + ): Person_KeyOutput! @fdc_generated(from: "Person", purpose: UPSERT_SINGLE) + """ + ✨ Insert or update a single `Thing` into the table, based on the primary key. Returns the key of the newly inserted `Thing`. + """ + thing_upsert( + """ + Data object to insert or update if it already exists. + """ + data: Thing_Data! + ): Thing_KeyOutput! @fdc_generated(from: "Thing", purpose: UPSERT_SINGLE) + """ + ✨ Insert or update a single `TimestampHolder` into the table, based on the primary key. Returns the key of the newly inserted `TimestampHolder`. + """ + timestampHolder_upsert( + """ + Data object to insert or update if it already exists. + """ + data: TimestampHolder_Data! + ): TimestampHolder_KeyOutput! @fdc_generated(from: "TimestampHolder", purpose: UPSERT_SINGLE) + """ + ✨ Update a single `DirectedBy` based on `id`, `key` or `first`, setting columns specified in `data`. Returns `null` if not found. + """ + directedBy_update( + """ + The key used to identify the object. + """ + key: DirectedBy_Key + + """ + Fetch the first row based on the filters and ordering. + """ + first: DirectedBy_FirstRow + + """ + Data object containing fields to be updated. + """ + data: DirectedBy_Data! + ): DirectedBy_KeyOutput @fdc_generated(from: "DirectedBy", purpose: UPDATE_SINGLE) + """ + ✨ Update a single `Movie` based on `id`, `key` or `first`, setting columns specified in `data`. Returns `null` if not found. + """ + movie_update( + """ + The unique ID of the object. + """ + id: UUID + + """ + The key used to identify the object. + """ + key: Movie_Key + + """ + Fetch the first row based on the filters and ordering. + """ + first: Movie_FirstRow + + """ + Data object containing fields to be updated. + """ + data: Movie_Data! + ): Movie_KeyOutput @fdc_generated(from: "Movie", purpose: UPDATE_SINGLE) + """ + ✨ Update a single `Person` based on `id`, `key` or `first`, setting columns specified in `data`. Returns `null` if not found. + """ + person_update( + """ + The unique ID of the object. + """ + id: UUID + + """ + The key used to identify the object. + """ + key: Person_Key + + """ + Fetch the first row based on the filters and ordering. + """ + first: Person_FirstRow + + """ + Data object containing fields to be updated. + """ + data: Person_Data! + ): Person_KeyOutput @fdc_generated(from: "Person", purpose: UPDATE_SINGLE) + """ + ✨ Update a single `Thing` based on `id`, `key` or `first`, setting columns specified in `data`. Returns `null` if not found. + """ + thing_update( + """ + The unique ID of the object. + """ + id: UUID + + """ + The key used to identify the object. + """ + key: Thing_Key + + """ + Fetch the first row based on the filters and ordering. + """ + first: Thing_FirstRow + + """ + Data object containing fields to be updated. + """ + data: Thing_Data! + ): Thing_KeyOutput @fdc_generated(from: "Thing", purpose: UPDATE_SINGLE) + """ + ✨ Update a single `TimestampHolder` based on `id`, `key` or `first`, setting columns specified in `data`. Returns `null` if not found. + """ + timestampHolder_update( + """ + The key used to identify the object. + """ + key: TimestampHolder_Key + + """ + Fetch the first row based on the filters and ordering. + """ + first: TimestampHolder_FirstRow + + """ + Data object containing fields to be updated. + """ + data: TimestampHolder_Data! + ): TimestampHolder_KeyOutput @fdc_generated(from: "TimestampHolder", purpose: UPDATE_SINGLE) + """ + ✨ Update `DirectedBy` objects matching `where` conditions (or `all`, if true) according to `data`. Returns the number of rows updated. + """ + directedBy_updateMany( + """ + Filter condition to specify which rows to update. + """ + where: DirectedBy_Filter + + """ + Set to true to update all rows. + """ + all: Boolean = false + + """ + Data object containing fields to update. + """ + data: DirectedBy_Data! + ): Int! @fdc_generated(from: "DirectedBy", purpose: UPDATE_MULTIPLE) + """ + ✨ Update `Movie` objects matching `where` conditions (or `all`, if true) according to `data`. Returns the number of rows updated. + """ + movie_updateMany( + """ + Filter condition to specify which rows to update. + """ + where: Movie_Filter + + """ + Set to true to update all rows. + """ + all: Boolean = false + + """ + Data object containing fields to update. + """ + data: Movie_Data! + ): Int! @fdc_generated(from: "Movie", purpose: UPDATE_MULTIPLE) + """ + ✨ Update `Person` objects matching `where` conditions (or `all`, if true) according to `data`. Returns the number of rows updated. + """ + person_updateMany( + """ + Filter condition to specify which rows to update. + """ + where: Person_Filter + + """ + Set to true to update all rows. + """ + all: Boolean = false + + """ + Data object containing fields to update. + """ + data: Person_Data! + ): Int! @fdc_generated(from: "Person", purpose: UPDATE_MULTIPLE) + """ + ✨ Update `Thing` objects matching `where` conditions (or `all`, if true) according to `data`. Returns the number of rows updated. + """ + thing_updateMany( + """ + Filter condition to specify which rows to update. + """ + where: Thing_Filter + + """ + Set to true to update all rows. + """ + all: Boolean = false + + """ + Data object containing fields to update. + """ + data: Thing_Data! + ): Int! @fdc_generated(from: "Thing", purpose: UPDATE_MULTIPLE) + """ + ✨ Update `TimestampHolder` objects matching `where` conditions (or `all`, if true) according to `data`. Returns the number of rows updated. + """ + timestampHolder_updateMany( + """ + Filter condition to specify which rows to update. + """ + where: TimestampHolder_Filter + + """ + Set to true to update all rows. + """ + all: Boolean = false + + """ + Data object containing fields to update. + """ + data: TimestampHolder_Data! + ): Int! @fdc_generated(from: "TimestampHolder", purpose: UPDATE_MULTIPLE) + """ + ✨ Delete a single `DirectedBy` based on `id`, `key` or `first` and return its key (or `null` if not found). + """ + directedBy_delete( + """ + The key used to identify the object. + """ + key: DirectedBy_Key + + """ + Fetch the first row based on the filters and ordering. + """ + first: DirectedBy_FirstRow + ): DirectedBy_KeyOutput @fdc_generated(from: "DirectedBy", purpose: DELETE_SINGLE) + """ + ✨ Delete a single `Movie` based on `id`, `key` or `first` and return its key (or `null` if not found). + """ + movie_delete( + """ + The unique ID of the object. + """ + id: UUID + + """ + The key used to identify the object. + """ + key: Movie_Key + + """ + Fetch the first row based on the filters and ordering. + """ + first: Movie_FirstRow + ): Movie_KeyOutput @fdc_generated(from: "Movie", purpose: DELETE_SINGLE) + """ + ✨ Delete a single `Person` based on `id`, `key` or `first` and return its key (or `null` if not found). + """ + person_delete( + """ + The unique ID of the object. + """ + id: UUID + + """ + The key used to identify the object. + """ + key: Person_Key + + """ + Fetch the first row based on the filters and ordering. + """ + first: Person_FirstRow + ): Person_KeyOutput @fdc_generated(from: "Person", purpose: DELETE_SINGLE) + """ + ✨ Delete a single `Thing` based on `id`, `key` or `first` and return its key (or `null` if not found). + """ + thing_delete( + """ + The unique ID of the object. + """ + id: UUID + + """ + The key used to identify the object. + """ + key: Thing_Key + + """ + Fetch the first row based on the filters and ordering. + """ + first: Thing_FirstRow + ): Thing_KeyOutput @fdc_generated(from: "Thing", purpose: DELETE_SINGLE) + """ + ✨ Delete a single `TimestampHolder` based on `id`, `key` or `first` and return its key (or `null` if not found). + """ + timestampHolder_delete( + """ + The key used to identify the object. + """ + key: TimestampHolder_Key + + """ + Fetch the first row based on the filters and ordering. + """ + first: TimestampHolder_FirstRow + ): TimestampHolder_KeyOutput @fdc_generated(from: "TimestampHolder", purpose: DELETE_SINGLE) + """ + ✨ Delete `DirectedBy` objects matching `where` conditions (or `all`, if true). Returns the number of rows deleted. + """ + directedBy_deleteMany( + """ + Filter condition to specify which rows to delete. + """ + where: DirectedBy_Filter + + """ + Set to true to delete all rows. + """ + all: Boolean = false + ): Int! @fdc_generated(from: "DirectedBy", purpose: DELETE_MULTIPLE) + """ + ✨ Delete `Movie` objects matching `where` conditions (or `all`, if true). Returns the number of rows deleted. + """ + movie_deleteMany( + """ + Filter condition to specify which rows to delete. + """ + where: Movie_Filter + + """ + Set to true to delete all rows. + """ + all: Boolean = false + ): Int! @fdc_generated(from: "Movie", purpose: DELETE_MULTIPLE) + """ + ✨ Delete `Person` objects matching `where` conditions (or `all`, if true). Returns the number of rows deleted. + """ + person_deleteMany( + """ + Filter condition to specify which rows to delete. + """ + where: Person_Filter + + """ + Set to true to delete all rows. + """ + all: Boolean = false + ): Int! @fdc_generated(from: "Person", purpose: DELETE_MULTIPLE) + """ + ✨ Delete `Thing` objects matching `where` conditions (or `all`, if true). Returns the number of rows deleted. + """ + thing_deleteMany( + """ + Filter condition to specify which rows to delete. + """ + where: Thing_Filter + + """ + Set to true to delete all rows. + """ + all: Boolean = false + ): Int! @fdc_generated(from: "Thing", purpose: DELETE_MULTIPLE) + """ + ✨ Delete `TimestampHolder` objects matching `where` conditions (or `all`, if true). Returns the number of rows deleted. + """ + timestampHolder_deleteMany( + """ + Filter condition to specify which rows to delete. + """ + where: TimestampHolder_Filter + + """ + Set to true to delete all rows. + """ + all: Boolean = false + ): Int! @fdc_generated(from: "TimestampHolder", purpose: DELETE_MULTIPLE) +} diff --git a/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/main/query.gql b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/main/query.gql new file mode 100644 index 000000000000..fefe75242191 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/main/query.gql @@ -0,0 +1,207 @@ +extend type Query { + """ + ✨ Look up a single `DirectedBy` based on `id`, `key` or `first` and return selected fields (or `null` if not found). + """ + directedBy( + """ + The key used to identify the object. + """ + key: DirectedBy_Key + + """ + Fetch the first row based on the filters and ordering. + """ + first: DirectedBy_FirstRow + ): DirectedBy @fdc_generated(from: "DirectedBy", purpose: QUERY_SINGLE) + """ + ✨ Look up a single `Movie` based on `id`, `key` or `first` and return selected fields (or `null` if not found). + """ + movie( + """ + The unique ID of the object. + """ + id: UUID + + """ + The key used to identify the object. + """ + key: Movie_Key + + """ + Fetch the first row based on the filters and ordering. + """ + first: Movie_FirstRow + ): Movie @fdc_generated(from: "Movie", purpose: QUERY_SINGLE) + """ + ✨ Look up a single `Person` based on `id`, `key` or `first` and return selected fields (or `null` if not found). + """ + person( + """ + The unique ID of the object. + """ + id: UUID + + """ + The key used to identify the object. + """ + key: Person_Key + + """ + Fetch the first row based on the filters and ordering. + """ + first: Person_FirstRow + ): Person @fdc_generated(from: "Person", purpose: QUERY_SINGLE) + """ + ✨ Look up a single `Thing` based on `id`, `key` or `first` and return selected fields (or `null` if not found). + """ + thing( + """ + The unique ID of the object. + """ + id: UUID + + """ + The key used to identify the object. + """ + key: Thing_Key + + """ + Fetch the first row based on the filters and ordering. + """ + first: Thing_FirstRow + ): Thing @fdc_generated(from: "Thing", purpose: QUERY_SINGLE) + """ + ✨ Look up a single `TimestampHolder` based on `id`, `key` or `first` and return selected fields (or `null` if not found). + """ + timestampHolder( + """ + The key used to identify the object. + """ + key: TimestampHolder_Key + + """ + Fetch the first row based on the filters and ordering. + """ + first: TimestampHolder_FirstRow + ): TimestampHolder @fdc_generated(from: "TimestampHolder", purpose: QUERY_SINGLE) + """ + ✨ List `DirectedBy` objects in the table, optionally filtered by `where` conditions. + """ + directedBies( + """ + Filter condition to narrow down the query results. + """ + where: DirectedBy_Filter + + """ + Order the query results by specific fields. + """ + orderBy: [DirectedBy_Order!] + + """ + Number of rows to skip before starting to return the results. + """ + offset: Int + + """ + Maximum number of rows to return (defaults to 100 rows). + """ + limit: Int = 100 + ): [DirectedBy!]! @fdc_generated(from: "DirectedBy", purpose: QUERY_MULTIPLE) + """ + ✨ List `Movie` objects in the table, optionally filtered by `where` conditions. + """ + movies( + """ + Filter condition to narrow down the query results. + """ + where: Movie_Filter + + """ + Order the query results by specific fields. + """ + orderBy: [Movie_Order!] + + """ + Number of rows to skip before starting to return the results. + """ + offset: Int + + """ + Maximum number of rows to return (defaults to 100 rows). + """ + limit: Int = 100 + ): [Movie!]! @fdc_generated(from: "Movie", purpose: QUERY_MULTIPLE) + """ + ✨ List `Person` objects in the table, optionally filtered by `where` conditions. + """ + people( + """ + Filter condition to narrow down the query results. + """ + where: Person_Filter + + """ + Order the query results by specific fields. + """ + orderBy: [Person_Order!] + + """ + Number of rows to skip before starting to return the results. + """ + offset: Int + + """ + Maximum number of rows to return (defaults to 100 rows). + """ + limit: Int = 100 + ): [Person!]! @fdc_generated(from: "Person", purpose: QUERY_MULTIPLE) + """ + ✨ List `Thing` objects in the table, optionally filtered by `where` conditions. + """ + things( + """ + Filter condition to narrow down the query results. + """ + where: Thing_Filter + + """ + Order the query results by specific fields. + """ + orderBy: [Thing_Order!] + + """ + Number of rows to skip before starting to return the results. + """ + offset: Int + + """ + Maximum number of rows to return (defaults to 100 rows). + """ + limit: Int = 100 + ): [Thing!]! @fdc_generated(from: "Thing", purpose: QUERY_MULTIPLE) + """ + ✨ List `TimestampHolder` objects in the table, optionally filtered by `where` conditions. + """ + timestampHolders( + """ + Filter condition to narrow down the query results. + """ + where: TimestampHolder_Filter + + """ + Order the query results by specific fields. + """ + orderBy: [TimestampHolder_Order!] + + """ + Number of rows to skip before starting to return the results. + """ + offset: Int + + """ + Maximum number of rows to return (defaults to 100 rows). + """ + limit: Int = 100 + ): [TimestampHolder!]! @fdc_generated(from: "TimestampHolder", purpose: QUERY_MULTIPLE) +} diff --git a/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/main/relation.gql b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/main/relation.gql new file mode 100644 index 000000000000..7e46ed3716e0 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/main/relation.gql @@ -0,0 +1,100 @@ +extend type Movie { + """ + ✨ List `DirectedBy` objects in a one-to-many relationship (where `DirectedBy`.`movie` is this object). + """ + directedBies_on_movie( + """ + Filter condition to narrow down the query results. + """ + where: DirectedBy_Filter + + """ + Order the query results by specific fields. + """ + orderBy: [DirectedBy_Order!] + + """ + Number of rows to skip before starting to return the results. + """ + offset: Int + + """ + Maximum number of rows to return (defaults to 100 rows). + """ + limit: Int = 100 + ): [DirectedBy!]! @fdc_generated(from: "DirectedBy.movie", purpose: QUERY_MULTIPLE_ONE_TO_MANY) + """ + ✨ List `Person` objects using `DirectedBy` as the join table (a `DirectedBy` object exists where its `movie` is this and its `directedby` is that). + """ + people_via_DirectedBy( + """ + Filter condition to narrow down the query results. + """ + where: DirectedBy_Filter + + """ + Order the query results by specific fields. + """ + orderBy: [DirectedBy_Order!] + + """ + Number of rows to skip before starting to return the results. + """ + offset: Int + + """ + Maximum number of rows to return (defaults to 100 rows). + """ + limit: Int = 100 + ): [Person!]! @fdc_generated(from: "DirectedBy", purpose: QUERY_MULTIPLE_MANY_TO_MANY) +} +extend type Person { + """ + ✨ List `DirectedBy` objects in a one-to-many relationship (where `DirectedBy`.`directedby` is this object). + """ + directedBies_on_directedby( + """ + Filter condition to narrow down the query results. + """ + where: DirectedBy_Filter + + """ + Order the query results by specific fields. + """ + orderBy: [DirectedBy_Order!] + + """ + Number of rows to skip before starting to return the results. + """ + offset: Int + + """ + Maximum number of rows to return (defaults to 100 rows). + """ + limit: Int = 100 + ): [DirectedBy!]! @fdc_generated(from: "DirectedBy.directedby", purpose: QUERY_MULTIPLE_ONE_TO_MANY) + """ + ✨ List `Movie` objects using `DirectedBy` as the join table (a `DirectedBy` object exists where its `directedby` is this and its `movie` is that). + """ + movies_via_DirectedBy( + """ + Filter condition to narrow down the query results. + """ + where: DirectedBy_Filter + + """ + Order the query results by specific fields. + """ + orderBy: [DirectedBy_Order!] + + """ + Number of rows to skip before starting to return the results. + """ + offset: Int + + """ + Maximum number of rows to return (defaults to 100 rows). + """ + limit: Int = 100 + ): [Movie!]! @fdc_generated(from: "DirectedBy", purpose: QUERY_MULTIPLE_MANY_TO_MANY) +} diff --git a/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/prelude.gql b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/prelude.gql new file mode 100644 index 000000000000..d6867cc45ed4 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/.dataconnect/schema/prelude.gql @@ -0,0 +1,1865 @@ +"AccessLevel specifies coarse access policies for common situations." +enum AccessLevel { + """ + This operation is accessible to anyone, with or without authentication. + Equivalent to: @auth(expr: "true") + """ + PUBLIC + + """ + This operation can be executed only with a valid Firebase Auth ID token. + **Note:** This access level allows anonymous and unverified accounts, + which may present security and abuse risks. + Equivalent to: `@auth(expr: "auth.uid != nil")` + """ + USER_ANON + + """ + This operation is restricted to non-anonymous Firebase Auth accounts. + Equivalent to: `@auth(expr: "auth.uid != nil && auth.token.firebase.sign_in_provider != 'anonymous'")` + """ + USER + + """ + This operation is restricted to Firebase Auth accounts with verified email addresses. + Equivalent to: `@auth(expr: "auth.uid != nil && auth.token.email_verified")` + """ + USER_EMAIL_VERIFIED + + """ + This operation cannot be executed by anyone. The operation can only be performed + by using the Admin SDK from a privileged environment. + Equivalent to: `@auth(expr: "false")` + """ + NO_ACCESS +} + +""" +The `@auth` directive defines the authentication policy for a query or mutation. + +It must be added to any operation that you wish to be accessible from a client +application. If not specified, the operation defaults to `@auth(level: NO_ACCESS)`. + +Refer to [Data Connect Auth Guide](https://firebase.google.com/docs/data-connect/authorization-and-security) for the best practices. +""" +directive @auth( + """ + The minimal level of access required to perform this operation. + Exactly one of `level` and `expr` should be specified. + """ + level: AccessLevel @fdc_oneOf(required: true) + """ + A CEL expression that grants access to this operation if the expression + evaluates to `true`. + Exactly one of `level` and `expr` should be specified. + """ + expr: Boolean_Expr @fdc_oneOf(required: true) +) on QUERY | MUTATION + + +""" +Require that this mutation always run in a DB transaction. + +Mutations with `@transaction` are guaranteed to either fully succeed or fully +fail. If any of the fields within the transaction fails, the entire transaction +is rolled back. From a client standpoint, any failure behaves as if the entire +request had failed with a request error and execution had not begun. + +Mutations without `@transaction` would execute each root field one after +another in sequence. It surfaces any errors as partial [field errors](https://spec.graphql.org/October2021/#sec-Errors.Field-errors), +but not impacts the subsequent executions. + +The `@transaction` directive cannot be added to queries for now. +Currently, queries cannot fail partially, the response data is not guaranteed +to be a consistent snapshot. +""" +directive @transaction on MUTATION + +"Query filter criteria for `String` scalar fields." +input String_Filter { + "When true, match if field `IS NULL`. When false, match if field is `NOT NULL`." + isNull: Boolean + "Match if field is exactly equal to provided value." + eq: String @fdc_oneOf(group: "eq") + """ + Match if field is exactly equal to the result of the provided server value + expression. Currently only `auth.uid` is supported as an expression. + """ + eq_expr: String_Expr @fdc_oneOf(group: "eq") + "Match if field is not equal to provided value." + ne: String @fdc_oneOf(group: "ne") + """ + Match if field is not equal to the result of the provided server value + expression. Currently only `auth.uid` is supported as an expression. + """ + ne_expr: String_Expr @fdc_oneOf(group: "ne") + "Match if field value is among the provided list of values." + in: [String!] + "Match if field value is not among the provided list of values." + nin: [String!] + "Match if field value is greater than the provided value." + gt: String + "Match if field value is greater than or equal to the provided value." + ge: String + "Match if field value is less than the provided value." + lt: String + "Match if field value is less than or equal to the provided value." + le: String + """ + Match if field value contains the provided value as a substring. Equivalent + to `LIKE '%value%'` + """ + contains: String + """ + Match if field value starts with the provided value. Equivalent to + `LIKE 'value%'` + """ + startsWith: String + """ + Match if field value ends with the provided value. Equivalent to + `LIKE '%value'` + """ + endsWith: String + """ + Match if field value matches the provided pattern. See `String_Pattern` for + more details. + """ + pattern: String_Pattern +} + +""" +The pattern match condition on a string. Specify either like or regex. +https://www.postgresql.org/docs/current/functions-matching.html +""" +input String_Pattern { + "Match using the provided `LIKE` expression." + like: String + "Match using the provided POSIX regular expression." + regex: String + "When true, ignore case when matching." + ignoreCase: Boolean + "When true, invert the match result. Equivalent to `NOT LIKE` or `!~`." + invert: Boolean +} + +"Query filter criteris for `[String!]` scalar fields." +input String_ListFilter { + "Match if list field contains the provided value as a member." + includes: String + "Match if list field does not contain the provided value as a member." + excludes: String + "Match if list field contains all of the provided values as members." + includesAll: [String!] + "Match if list field does not contain any of the provided values as members." + excludesAll: [String!] +} + +"Query filter criteria for `UUID` scalar fields." +input UUID_Filter { + "When true, match if field `IS NULL`. When false, match if field is `NOT NULL`." + isNull: Boolean + "Match if field is exactly equal to provided value." + eq: UUID + "Match if field is not equal to provided value." + ne: UUID + "Match if field value is among the provided list of values." + in: [UUID!] + "Match if field value is not among the provided list of values." + nin: [UUID!] +} + +"Query filter criteris for `[UUID!]` scalar fields." +input UUID_ListFilter { + "Match if list field contains the provided value as a member." + includes: UUID + "Match if list field does not contain the provided value as a member." + excludes: UUID + "Match if list field contains all of the provided values as members." + includesAll: [UUID!] + "Match if list field does not contain any of the provided values as members." + excludesAll: [UUID!] +} + +"Query filter criteria for `Int` scalar fields." +input Int_Filter { + "When true, match if field `IS NULL`. When false, match if field is `NOT NULL`." + isNull: Boolean + "Match if field is exactly equal to provided value." + eq: Int + "Match if field is not equal to provided value." + ne: Int + "Match if field value is among the provided list of values." + in: [Int!] + "Match if field value is not among the provided list of values." + nin: [Int!] + "Match if field value is greater than the provided value." + gt: Int + "Match if field value is greater than or equal to the provided value." + ge: Int + "Match if field value is less than the provided value." + lt: Int + "Match if field value is less than or equal to the provided value." + le: Int +} + +"Query filter criteris for `[Int!]` scalar fields." +input Int_ListFilter { + "Match if list field contains the provided value as a member." + includes: Int + "Match if list field does not contain the provided value as a member." + excludes: Int + "Match if list field contains all of the provided values as members." + includesAll: [Int!] + "Match if list field does not contain any of the provided values as members." + excludesAll: [Int!] +} + +"Query filter criteria for `Int64` scalar fields." +input Int64_Filter { + "When true, match if field `IS NULL`. When false, match if field is `NOT NULL`." + isNull: Boolean + "Match if field is exactly equal to provided value." + eq: Int64 + "Match if field is not equal to provided value." + ne: Int64 + "Match if field value is among the provided list of values." + in: [Int64!] + "Match if field value is not among the provided list of values." + nin: [Int64!] + "Match if field value is greater than the provided value." + gt: Int64 + "Match if field value is greater than or equal to the provided value." + ge: Int64 + "Match if field value is less than the provided value." + lt: Int64 + "Match if field value is less than or equal to the provided value." + le: Int64 +} + +"Query filter criteria for `[Int64!]` scalar fields." +input Int64_ListFilter { + "Match if list field contains the provided value as a member." + includes: Int64 + "Match if list field does not contain the provided value as a member." + excludes: Int64 + "Match if list field contains all of the provided values as members." + includesAll: [Int64!] + "Match if list field does not contain any of the provided values as members." + excludesAll: [Int64!] +} + +"Query filter criteria for `Float` scalar fields." +input Float_Filter { + "When true, match if field `IS NULL`. When false, match if field is `NOT NULL`." + isNull: Boolean + "Match if field is exactly equal to provided value." + eq: Float + "Match if field is not equal to provided value." + ne: Float + "Match if field value is among the provided list of values." + in: [Float!] + "Match if field value is not among the provided list of values." + nin: [Float!] + "Match if field value is greater than the provided value." + gt: Float + "Match if field value is greater than or equal to the provided value." + ge: Float + "Match if field value is less than the provided value." + lt: Float + "Match if field value is less than or equal to the provided value." + le: Float +} + +"Query filter criteria for `[Float!]` scalar fields." +input Float_ListFilter { + "Match if list field contains the provided value as a member." + includes: Float + "Match if list field does not contain the provided value as a member." + excludes: Float + "Match if list field contains all of the provided values as members." + includesAll: [Float!] + "Match if list field does not contain any of the provided values as members." + excludesAll: [Float!] +} + +"Query filter criteria for `Boolean` scalar fields." +input Boolean_Filter { + "When true, match if field `IS NULL`. When false, match if field is `NOT NULL`." + isNull: Boolean + "Match if field is exactly equal to provided value." + eq: Boolean + "Match if field is not equal to provided value." + ne: Boolean + "Match if field value is among the provided list of values." + in: [Boolean!] + "Match if field value is not among the provided list of values." + nin: [Boolean!] +} + +"Query filter criteria for `[Boolean!]` scalar fields." +input Boolean_ListFilter { + "Match if list field contains the provided value as a member." + includes: Boolean + "Match if list field does not contain the provided value as a member." + excludes: Boolean + "Match if list field contains all of the provided values as members." + includesAll: [Boolean!] + "Match if list field does not contain any of the provided values as members." + excludesAll: [Boolean!] +} + +"Query filter criteria for `Any` scalar fields." +input Any_Filter { + "When true, match if field `IS NULL`. When false, match if field is `NOT NULL`." + isNull: Boolean + "Match if field is exactly equal to provided value." + eq: Any + "Match if field is not equal to provided value." + ne: Any + "Match if field value is among the provided list of values." + in: [Any!] + "Match if field value is not among the provided list of values." + nin: [Any!] +} + +"Query filter criteria for `[Any!]` scalar fields." +input Any_ListFilter { + "Match if list field contains the provided value as a member." + includes: Any + "Match if list field does not contain the provided value as a member." + excludes: Any + "Match if list field contains all of the provided values as members." + includesAll: [Any!] + "Match if list field does not contain any of the provided values as members." + excludesAll: [Any!] +} + +""" +(Internal) A string that uniquely identifies a type, field, and so on. + +The most common usage in FDC is `SomeType` or `SomeType.someField`. See the +linked page in the @specifiedBy directive for the GraphQL RFC with more details. +""" +scalar SchemaCoordinate + @specifiedBy(url: "https://github.com/graphql/graphql-wg/blob/6d02705dea034fb65ebc6799632adb7bd550d0aa/rfcs/SchemaCoordinates.md") + @fdc_forbiddenAsFieldType + @fdc_forbiddenAsVariableType + +"(Internal) The purpose of a generated type or field." +enum GeneratedPurpose { + # Implicit fields added to the table types as columns. + IMPLICIT_KEY_FIELD + IMPLICIT_REF_FIELD + + # Relational non-column fields extended to table types. + QUERY_MULTIPLE_ONE_TO_MANY + QUERY_MULTIPLE_MANY_TO_MANY + + # Top-level Query fields. + QUERY_SINGLE + QUERY_MULTIPLE + QUERY_MULTIPLE_BY_SIMILARITY + + # Top-level Mutation fields. + INSERT_SINGLE + INSERT_MULTIPLE + UPSERT_SINGLE + UPSERT_MULTIPLE + UPDATE_SINGLE + UPDATE_MULTIPLE + DELETE_SINGLE + DELETE_MULTIPLE +} + +"(Internal) Added to definitions generated by FDC." +directive @fdc_generated( + "The source type or field that causes this definition to be generated." + from: SchemaCoordinate! + "The reason why this definition is generated, such as the intended use case." + purpose: GeneratedPurpose! +) on + | SCALAR + | OBJECT + | FIELD_DEFINITION + | ARGUMENT_DEFINITION + | INTERFACE + | UNION + | ENUM + | ENUM_VALUE + | INPUT_OBJECT + | INPUT_FIELD_DEFINITION + +type _Service { + "Full Service Definition Language of the Frebase Data Connect Schema, including normalized schema, predefined and generated types." + sdl: String! + "Orignal Schema Sources in the service." + schema: String! + "Generated documentation from the schema of the Firebase Data Connect Service." + docs: [_Doc!]! +} + +type _Doc { + "Name of the Doc Page." + page: String! + "The markdown content of the doc page." + markdown: String! +} + +"(Internal) Added to things that may be removed from FDC and will soon be no longer usable in schema or operations." +directive @fdc_deprecated(reason: String = "No longer supported") on + | SCHEMA + | SCALAR + | OBJECT + | FIELD_DEFINITION + | ARGUMENT_DEFINITION + | INTERFACE + | UNION + | ENUM + | ENUM_VALUE + | INPUT_OBJECT + | INPUT_FIELD_DEFINITION + +"(Internal) Added to scalars representing quoted CEL expressions." +directive @fdc_celExpression( + "The expected CEL type that the expression should evaluate to." + returnType: String +) on SCALAR + +"(Internal) Added to scalars representing quoted SQL expressions." +directive @fdc_sqlExpression( + "The expected SQL type that the expression should evaluate to." + dataType: String +) on SCALAR + +"(Internal) Added to types that may not be used as variables." +directive @fdc_forbiddenAsVariableType on SCALAR | OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT + +"(Internal) Added to types that may not be used as fields in schema." +directive @fdc_forbiddenAsFieldType on SCALAR | OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT + +"Provides a frequently used example for this type / field / argument." +directive @fdc_example( + "A GraphQL literal value (verbatim) whose type matches the target." + value: Any + "A human-readable text description of what `value` means in this context." + description: String +) repeatable on SCALAR | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION | INTERFACE | UNION | ENUM | INPUT_OBJECT | INPUT_FIELD_DEFINITION + +"(Internal) Marks this field / argument as conflicting with others in the same group." +directive @fdc_oneOf( + "The group name where fields / arguments conflict with each other." + group: String! = "" + "If true, exactly one field / argument in the group must be specified." + required: Boolean! = false +) repeatable on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION + +type Mutation { + # This is just a dummy field so that Mutation is always non-empty. + _firebase: Void @fdc_deprecated(reason: "dummy field -- does nothing useful") +} + +""" +`UUID` is a string of hexadecimal digits representing an RFC4122-compliant UUID. + +UUIDs are always output as 32 lowercase hexadecimal digits without delimiters or +curly braces. +Inputs in the following formats are also accepted (case insensitive): + +- `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` +- `urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` +- `{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}` + +In the PostgreSQL table, it's stored as [`uuid`](https://www.postgresql.org/docs/current/datatype-uuid.html). +""" +scalar UUID @specifiedBy(url: "https://tools.ietf.org/html/rfc4122") + +""" +`Int64` is a scalar that represents a 64-bit signed integer. + +In the PostgreSQL table, it's stored as [`bigint`](https://www.postgresql.org/docs/current/datatype-numeric.html). + +On the wire, it's encoded as string because 64-bit integer exceeds the range of JSON number. +""" +scalar Int64 + +""" +The `Any` scalar represents any valid [JSON value](https://www.json.org/json-en.html). +It can be an object, array, string, number, or boolean. + +Caution: JSON doesn't distinguish Int and Float. + +In the PostgreSQL table, it's stored as [`jsonb`](https://www.postgresql.org/docs/current/datatype-json.html). +""" +scalar Any @specifiedBy(url: "https://www.json.org/json-en.html") + +""" +The `Void` scalar type represents the absence of any value. It is typically used +in operations where no value is expected in return. +""" +scalar Void + +""" +The `True` scalar type only accepts the boolean value `true`. + +An optional field/argument typed as `True` may either be set +to `true` or omitted (not provided at all). The values `false` or `null` are not +accepted. +""" +scalar True + @fdc_forbiddenAsFieldType + @fdc_forbiddenAsVariableType + @fdc_example(value: true, description: "The only allowed value.") + +""" +A Common Expression Language (CEL) expression that returns a boolean at runtime. + +This expression can reference the `auth` variable, which is null when Firebase +Auth is not used. When Firebase Auth is used, the following fields are available: + + - `auth.uid`: The current user ID. + - `auth.token`: A map containing all token fields (e.g., claims). + +""" +scalar Boolean_Expr + @specifiedBy(url: "https://github.com/google/cel-spec") + @fdc_celExpression(returnType: "bool") + @fdc_forbiddenAsVariableType + @fdc_forbiddenAsFieldType + @fdc_example(value: "auth != null", description: "Allow only if a Firebase Auth user is present.") + +""" +A Common Expression Language (CEL) expression that returns a string at runtime. + +**Limitation**: Currently, only a limited set of expressions are supported. +""" +scalar String_Expr + @specifiedBy(url: "https://github.com/google/cel-spec") + @fdc_celExpression(returnType: "string") + @fdc_forbiddenAsVariableType + @fdc_forbiddenAsFieldType + @fdc_example(value: "auth.uid", description: "The ID of the currently logged in user in Firebase Auth. (Errors if not logged in.)") + @fdc_example(value: "uuidV4()", description: "Generates a new random UUID (version 4) string, formatted as 32 lower-case hex digits without delimiters.") + +""" +A Common Expression Language (CEL) expression that returns a UUID string at runtime. + +**Limitation**: Currently, only a limited set of expressions are supported. +""" +scalar UUID_Expr + @specifiedBy(url: "https://github.com/google/cel-spec") + @fdc_celExpression(returnType: "string") + @fdc_forbiddenAsVariableType + @fdc_forbiddenAsFieldType + @fdc_example(value: "uuidV4()", description: "Generates a new random UUID (version 4) every time.") + +""" +A Common Expression Language (CEL) expression whose return type is unspecified. + +**Limitation**: Only a limited set of expressions are currently supported for each +type. +""" +scalar Any_Expr + @specifiedBy(url: "https://github.com/google/cel-spec") + @fdc_celExpression + @fdc_forbiddenAsVariableType + @fdc_forbiddenAsFieldType + @fdc_example(value: "auth.uid", description: "The ID of the currently logged in user in Firebase Auth. (Errors if not logged in.)") + @fdc_example(value: "uuidV4()", description: "Generates a new random UUID version 4 (formatted as 32 lower-case hex digits without delimiters if result type is String).") + @fdc_example(value: "request.time", description: "The timestamp when the request is received (with microseconds precision).") + +""" +A PostgreSQL value expression whose return type is unspecified. +""" +scalar Any_SQL + @specifiedBy(url: "https://www.postgresql.org/docs/current/sql-expressions.html") + @fdc_sqlExpression + @fdc_forbiddenAsVariableType + @fdc_forbiddenAsFieldType + +""" +Defines a relational database table. + +In this example, we defined one table with a field named `myField`. + +```graphql +type TableName @table { + myField: String +} +``` +Data Connect adds an implicit `id` primary key column. So the above schema is equivalent to: + +```graphql +type TableName @table(key: "id") { + id: String @default(expr: "uuidV4()") + myField: String +} +``` + +Data Connect generates the following SQL table and CRUD operations to use it. + +```sql +CREATE TABLE "public"."table_name" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "my_field" text NULL, + PRIMARY KEY ("id") +) +``` + + * You can lookup a row: `query ($id: UUID!) { tableName(id: $id) { myField } } ` + * You can find rows using: `query tableNames(limit: 20) { myField }` + * You can insert a row: `mutation { tableName_insert(data: {myField: "foo"}) }` + * You can update a row: `mutation ($id: UUID!) { tableName_update(id: $id, data: {myField: "bar"}) }` + * You can delete a row: `mutation ($id: UUID!) { tableName_delete(id: $id) }` + +##### Customizations + +- `@table(singular)` and `@table(plural)` can customize the singular and plural name. +- `@table(name)` can customize the Postgres table name. +- `@table(key)` can customize the primary key field name and type. + +For example, the `User` table often has a `uid` as its primary key. + +```graphql +type User @table(key: "uid") { + uid: String! + name: String +} +``` + + * You can securely lookup a row: `query { user(key: {uid_expr: "auth.uid"}) { name } } ` + * You can securely insert a row: `mutation { user_insert(data: {uid_expr: "auth.uid" name: "Fred"}) }` + * You can securely update a row: `mutation { user_update(key: {uid_expr: "auth.uid"}, data: {name: "New Name"}) }` + * You can securely delete a row: `mutation { user_delete(key: {uid_expr: "auth.uid"}) }` + +`@table` type can be configured further with: + + - Custom SQL data types for columns. See `@col`. + - Add SQL indexes. See `@index`. + - Add SQL unique constraints. See `@unique`. + - Add foreign key constraints to define relations. See `@ref`. + +""" +directive @table( + """ + Configures the SQL database table name. Defaults to snake_case like `table_name`. + """ + name: String + """ + Configures the singular name. Defaults to the camelCase like `tableName`. + """ + singular: String + """ + Configures the plural name. Defaults to infer based on English plural pattern like `tableNames`. + """ + plural: String + """ + Defines the primary key of the table. Defaults to a single field named `id`. + If not present already, Data Connect adds an implicit field `id: UUID! @default(expr: "uuidV4()")`. + """ + key: [String!] +) on OBJECT + +""" +Defines a relational database Raw SQLview. + +Data Connect generates GraphQL queries with WHERE and ORDER BY clauses. +However, not all SQL features has native GraphQL equivalent. + +You can write **an arbitrary SQL SELECT statement**. Data Connect +would map Graphql fields on `@view` type to columns in your SELECT statement. + +* Scalar GQL fields (camelCase) should match a SQL column (snake_case) + in the SQL SELECT statement. +* Reference GQL field can point to another `@table` type. Similar to foreign key + defined with `@ref` on a `@table` type, a `@view` type establishes a relation + when `@ref(fields)` match `@ref(references)` on the target table. + +In this example, you can use `@view(sql)` to define an aggregation view on existing +table. + +```graphql +type User @table { + name: String + score: Int +} +type UserAggregation @view(sql: ''' + SELECT + COUNT(*) as count, + SUM(score) as sum, + AVG(score) as average, + PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY score) AS median, + (SELECT id FROM "user" LIMIT 1) as example_id + FROM "user" +''') { + count: Int + sum: Int + average: Float + median: Float + example: User + exampleId: UUID +} +``` + +###### Example: Query Raw SQL View + +```graphql +query { + userAggregations { + count sum average median + exampleId example { id } + } +} +``` + +##### One-to-One View + +An one-to-one companion `@view` can be handy if you want to argument a `@table` +with additional implied content. + +```graphql +type Restaurant @table { + name: String! +} +type Review @table { + restaurant: Restaurant! + rating: Int! +} +type RestaurantStats @view(sql: ''' + SELECT + restaurant_id, + COUNT(*) AS review_count, + AVG(rating) AS average_rating + FROM review + GROUP BY restaurant_id +''') { + restaurant: Restaurant @unique + reviewCount: Int + averageRating: Float +} +``` + +In this example, `@unique` convey the assumption that each `Restaurant` should +have only one `RestaurantStats` object. + +###### Example: Query One-to-One View + +```graphql +query ListRestaurants { + restaurants { + name + stats: restaurantStats_on_restaurant { + reviewCount + averageRating + } + } +} +``` + +###### Example: Filter based on One-to-One View + +```graphql +query BestRestaurants($minAvgRating: Float, $minReviewCount: Int) { + restaurants(where: { + restaurantStats_on_restaurant: { + averageRating: {ge: $minAvgRating} + reviewCount: {ge: $minReviewCount} + } + }) { name } +} +``` + +##### Customizations + +- One of `@view(sql)` or `@view(name)` should be defined. + `@view(name)` can refer to a persisted SQL view in the Postgres schema. +- `@view(singular)` and `@view(plural)` can customize the singular and plural name. + +`@view` type can be configured further: + + - `@unique` lets you define one-to-one relation. + - `@col` lets you customize SQL column mapping. For example, `@col(name: "column_in_select")`. + +##### Limitations + +Raw SQL view doesn't have a primary key, so it doesn't support lookup. Other +`@table` or `@view` cannot have `@ref` to a view either. + +View cannot be mutated. You can perform CRUD operations on the underlying +table to alter its content. + +**Important: Data Connect doesn't parse and validate SQL** + +- If the SQL view is invalid or undefined, related requests may fail. +- If the SQL view return incompatible types. Firebase Data Connect may surface + errors. +- If a field doesn't have a corresponding column in the SQL SELECT statement, + it will always be `null`. +- There is no way to ensure VIEW to TABLE `@ref` constraint. +- All fields must be nullable in case they aren't found in the SELECT statement + or in the referenced table. + +**Important: You should always test `@view`!** + +""" +directive @view( + """ + The SQL view name. If neither `name` nor `sql` are provided, defaults to the + snake_case of the singular type name. + `name` and `sql` cannot be specified at the same time. + """ + name: String @fdc_oneOf + """ + SQL `SELECT` statement used as the basis for this type. + SQL SELECT columns should use snake_case. GraphQL fields should use camelCase. + `name` and `sql` cannot be specified at the same time. + """ + sql: String @fdc_oneOf + """ + Configures the singular name. Defaults to the camelCase like `viewName`. + """ + singular: String + """ + Configures the plural name. Defaults to infer based on English plural pattern like `viewNames`. + """ + plural: String +) on OBJECT + +""" +Customizes a field that represents a SQL database table column. + +Data Connect maps scalar Fields on `@table` type to a SQL column of +corresponding data type. + +- scalar `UUID`: [`uuid`](https://www.postgresql.org/docs/current/datatype-uuid.html). +- scalar `String`: [`text`](https://www.postgresql.org/docs/current/datatype-character.html) +- scalar `Int`: [`int`](https://www.postgresql.org/docs/current/datatype-numeric.html) +- scalar `Int64`: [`bigint`](https://www.postgresql.org/docs/current/datatype-numeric.html) +- scalar `Float`: [`double precision`](https://www.postgresql.org/docs/current/datatype-numeric.html) +- scalar `Boolean`: [`boolean`](https://www.postgresql.org/docs/current/datatype-boolean.html) +- scalar `Date`: [`date`](https://www.postgresql.org/docs/current/datatype-datetime.html) +- scalar `Timestamp`: [`timestamptz`](https://www.postgresql.org/docs/current/datatype-datetime.html) +- scalar `Any`: [`jsonb`](https://www.postgresql.org/docs/current/datatype-json.html) +- scalar `Vector`: [`pgvector`](https://github.com/pgvector/pgvector) + +Array scalar fields are mapped to [Postgres arrays](https://www.postgresql.org/docs/current/arrays.html). + +###### Example: Serial Primary Key + +For example, you can define auto-increment primary key. + +```graphql +type Post @table { + id: Int! @col(name: "post_id", dataType: "serial") +} +``` + +Data Connect converts it to the following SQL table schema. + +```sql +CREATE TABLE "public"."post" ( + "post_id" serial NOT NULL, + PRIMARY KEY ("id") +) +``` + +###### Example: Vector + +```graphql +type Post @table { + content: String! @col(name: "post_content") + contentEmbedding: Vector! @col(size:768) +} +``` + +""" +directive @col( + """ + The SQL database column name. Defaults to snake_case of the field name. + """ + name: String + """ + Configures the custom SQL data type. + + Each GraphQL type can map to multiple SQL data types. + Refer to [Postgres supported data types](https://www.postgresql.org/docs/current/datatype.html). + """ + dataType: String + """ + Required on `Vector` columns. It specifies the length of the Vector. + `textembedding-gecko@003` model generates `Vector` of `@col(size:768)`. + """ + size: Int +) on FIELD_DEFINITION + + +""" +Defines a foreign key reference to another table. + +For example, we can define a many-to-one relation. + +```graphql +type ManyTable @table { + refField: OneTable! +} +type OneTable @table { + someField: String! +} +``` +Data Connect adds implicit foreign key column and relation query field. So the +above schema is equivalent to the following schema. + +```graphql +type ManyTable @table { + id: UUID! @default(expr: "uuidV4()") + refField: OneTable! @ref(fields: "refFieldId", references: "id") + refFieldId: UUID! +} +type OneTable @table { + id: UUID! @default(expr: "uuidV4()") + someField: UUID! + manyTables_on_refField: [ManyTable!]! +} +``` +Data Connect generates the necessary foreign key constraint. + +```graphql +CREATE TABLE "public"."many_table" ( + "id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "ref_field_id" uuid NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "many_table_ref_field_id_fkey" FOREIGN KEY ("ref_field_id") REFERENCES "public"."one_table" ("id") ON DELETE CASCADE +) +``` + +###### Example: Traverse the Reference Field + +```graphql +query ($id: UUID!) { + manyTable(id: $id) { + refField { id } + } +} +``` + +###### Example: Reverse Traverse the Reference field + +```graphql +query ($id: UUID!) { + oneTable(id: $id) { + manyTables_on_refField { id } + } +} +``` + +##### Optional Many-to-One Relation + +An optional foreign key reference will be set to null if the referenced row is deleted. + +In this example, if a `User` is deleted, the `assignee` and `reporter` +references will be set to null. + +```graphql +type Bug @table { + title: String! + assignee: User + reproter: User +} +``` + +##### Required Many-to-One Relation + +A required foreign key reference will cascade delete if the referenced row is +deleted. + +In this example, if a `Post` is deleted, associated comments will also be +deleted. + +```graphql +type Comment @table { + post: Post! + content: String! +} +``` + +##### Many To Many Relation + +You can define a many-to-many relation with a join table. + +```graphql +type Membership @table(key: ["group", "user"]) { + group: Group! + user: User! + role: String! @default(value: "member") +} + +type Group @table { name: String! } +type User @table { name: String! } +``` + +When Data Connect sees a table with two reference field as its primary key, it +knows this is a join table, so expands the many-to-many query field. + +```graphql +type Group @table { + name: String! + users_via_Membership: [User!]! + memberships_on_group: [Membership!]! +} +type User @table { + name: String! + groups_via_Membership: [Group!]! + memberships_on_user: [Membership!]! +} +``` + +###### Example: Transerse the Many-To-Many Relation + +```graphql +query ($id: UUID!) { + group(id: $id) { + users: users_via_Membership { + name + } + } +} +``` + +###### Example: Transerse to the Join Table + +```graphql +query ($id: UUID!) { + group(id: $id) { + memberships: memberships_on_group { + user { name } + role + } + } +} +``` + +##### One To One Relation + +You can even define a one-to-one relation with the help of `@unique` or `@table(key)`. + +```graphql +type User @table { + name: String +} +type Account @table { + user: User! @unique +} +# Alternatively, use primary key constraint. +type Account @table(key: "user") { + user: User! +} +``` + +###### Example: Transerse the Reference Field + +```graphql +query ($id: UUID!) { + account(id: $id) { + user { id } + } +} +``` + +###### Example: Reverse Tranverse the Reference field + +```graphql +query ($id: UUID!) { + user(id: $id) { + account_on_user { id } + } +} +``` + +##### Customizations + +- `@ref(constraintName)` can customize the SQL foreign key constraint name (`table_name_ref_field_fkey` above). +- `@ref(fields)` can customize the foreign key field names. +- `@ref(references)` can customize the constraint to reference other columns. + By default, `@ref(references)` is the primary key of the `@ref` table. + Other fields with `@unique` may also be referred in the foreign key constraint. + +""" +directive @ref( + "The SQL database foreign key constraint name. Defaults to {table_name}_{field_name}_fkey." + constraintName: String + """ + Foreign key fields. Defaults to {tableName}{PrimaryIdName}. + """ + fields: [String!] + "The fields that the foreign key references in the other table. Defaults to its primary key." + references: [String!] +) on FIELD_DEFINITION + +"Defines the direction of an orderby query" +enum OrderDirection { +"Results are ordered in ascending order." + ASC +"Results are ordered in descending order." + DESC +} + +""" +Specifies the default value for a column field. + +For example: + +```graphql +type User @table(key: "uid") { + uid: String! @default(expr: "auth.uid") + number: Int! @col(dataType: "serial") + createdAt: Date! @default(expr: "request.time") + role: String! @default(value: "Member") + credit: Int! @default(value: 100) +} +``` + +The supported arguments vary based on the field type. +""" +directive @default( + "A constant value validated against the field's GraphQL type during compilation." + value: Any @fdc_oneOf(required: true) + "A CEL expression whose return value must match the field's data type." + expr: Any_Expr @fdc_oneOf(required: true) + """ + A raw SQL expression, whose SQL data type must match the underlying column. + + The value is any variable-free expression (in particular, cross-references to + other columns in the current table are not allowed). Subqueries are not allowed either. + See [PostgreSQL defaults](https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-PARMS-DEFAULT) + for more details. + """ + sql: Any_SQL @fdc_oneOf(required: true) +) on FIELD_DEFINITION + +""" +Defines a database index to optimize query performance. + +```graphql +type User @table @index(fields: ["name", "phoneNumber"], order: [ASC, DESC]) { + name: String @index + phoneNumber: Int64 @index + tags: [String] @index # GIN Index +} +``` + +##### Single Field Index + +You can put `@index` on a `@col` field to create a SQL index. + +`@index(order)` matters little for single field indexes, as they can be scanned +in both directions. + +##### Composite Index + +You can put `@index(fields: [...])` on `@table` type to define composite indexes. + +`@index(order: [...])` can customize the index order to satisfy particular +filter and order requirement. + +""" +directive @index( + """ + Configure the SQL database index id. + + If not overridden, Data Connect generates the index name: + - `table_name_first_field_second_field_aa_idx` + - `table_name_only_field_name_idx` + """ + name: String + """ + Only allowed and required when used on a `@table` type. + Specifies the fields to create the index on. + """ + fields: [String!] + """ + Only allowed for `BTREE` `@index` on `@table` type. + Specifies the order for each indexed column. Defaults to all `ASC`. + """ + order: [IndexFieldOrder!] + """ + Customize the index type. + + For most index, it defaults to `BTREE`. + For array fields, only allowed `IndexType` is `GIN`. + For `Vector` fields, defaults to `HNSW`, may configure to `IVFFLAT`. + """ + type: IndexType + """ + Only allowed when used on vector field. + Defines the vector similarity method. Defaults to `INNER_PRODUCT`. + """ + vector_method: VectorSimilarityMethod +) repeatable on FIELD_DEFINITION | OBJECT + +"Specifies the sorting order for database indexes." +enum IndexFieldOrder { + "Sorts the field in ascending order (from lowest to highest)." + ASC + "Sorts the field in descending order (from highest to lowest)." + DESC +} + +"Defines the type of index to be used in the database." +enum IndexType { + "A general-purpose index type commonly used for sorting and searching." + BTREE + "Generalized Inverted Index, optimized for indexing composite values such as arrays." + GIN + "Hierarchical Navigable Small World graph, used for nearest-neighbor searches on vector fields." + HNSW + "Inverted File Index, optimized for approximate nearest-neighbor searches in vector databases." + IVFFLAT +} + +""" +Defines unique constraints on `@table`. + +For example, + +```graphql +type User @table { + phoneNumber: Int64 @unique +} +type UserProfile @table { + user: User! @unique + address: String @unique +} +``` + +- `@unique` on a `@col` field adds a single-column unique constraint. +- `@unique` on a `@table` type adds a composite unique constraint. +- `@unique` on a `@ref` defines a one-to-one relation. It adds unique constraint + on `@ref(fields)`. + +`@unique` ensures those fields can uniquely identify a row, so other `@table` +type may define `@ref(references)` to refer to fields that has a unique constraint. + +""" +directive @unique( + """ + Configures the SQL database unique constraint name. + + If not overridden, Data Connect generates the unique constraint name: + - `table_name_first_field_second_field_uidx` + - `table_name_only_field_name_uidx` + """ + indexName: String + """ + Only allowed and required when used on OBJECT, + this specifies the fields to create a unique constraint on. + """ + fields: [String!] +) repeatable on FIELD_DEFINITION | OBJECT + +""" +Date is a string in the YYYY-MM-DD format representing a local-only date. + +See the description for Timestamp for range and limitations. + +As a FDC-specific extension, inputs that includes time portions (as specified by +the Timestamp scalar) are accepted but only the date portion is used. In other +words, only the part before "T" is used and the rest discarded. This effectively +truncates it to the local date in the specified time-zone. + +Outputs will always be in the canonical YYYY-MM-DD format. + +In the PostgreSQL table, it's stored as [`date`](https://www.postgresql.org/docs/current/datatype-datetime.html). +""" +scalar Date @specifiedBy(url: "https://scalars.graphql.org/andimarek/local-date.html") + +""" +Timestamp is a RFC 3339 string that represents an exact point in time. + +The serialization format follows https://scalars.graphql.org/andimarek/date-time +except the "Non-optional exact milliseconds" Section. As a FDC-specific +extension, inputs and outputs may contain 0, 3, 6, or 9 fractional digits. + +Specifically, output precision varies by server-side factors such as data source +support and clients must not rely on an exact number of digits. Clients may +truncate extra digits as fit, with the caveat that there may be information loss +if the truncated value is subsequently sent back to the server. + +FDC only supports year 1583 to 9999 (inclusive) and uses the ISO-8601 calendar +system for all date-time calculations. Notably, the expanded year representation +(+/-YYYYY) is rejected and Year 1582 and before may either be rejected or cause +undefined behavior. + +In the PostgreSQL table, it's stored as [`timestamptz`](https://www.postgresql.org/docs/current/datatype-datetime.html). +""" +scalar Timestamp @specifiedBy(url: "https://scalars.graphql.org/andimarek/date-time") + +""" +A Common Expression Language (CEL) expression that returns a Timestamp at runtime. + +Limitation: Right now, only a few expressions are supported. +""" +scalar Timestamp_Expr + @specifiedBy(url: "https://github.com/google/cel-spec") + @fdc_celExpression(returnType: "google.protobuf.Timestamp") + @fdc_forbiddenAsVariableType + @fdc_forbiddenAsFieldType + @fdc_example(value: "request.time", description: "The timestamp when the request is received (with microseconds precision).") + +""" +A Common Expression Language (CEL) expression that returns a Timestamp at runtime, +which is then truncated to UTC date only. The time-of-day parts are discarded. + +Limitation: Right now, only a few expressions are supported. +""" +scalar Date_Expr + @specifiedBy(url: "https://github.com/google/cel-spec") + @fdc_celExpression(returnType: "google.protobuf.Timestamp") + @fdc_forbiddenAsVariableType + @fdc_forbiddenAsFieldType + @fdc_example(value: "request.time", description: "The UTC date on which the request is received.") + +"Conditions on a `Date` value." +input Date_Filter { + "Match if the field `IS NULL`." + isNull: Boolean + "Match if the field is exactly equal to the provided value." + eq: Date @fdc_oneOf(group: "eq") + "Match if the field equals the provided CEL expression." + eq_expr: Date_Expr @fdc_oneOf(group: "eq") + "Match if the field equals the provided relative date." + eq_date: Date_Relative @fdc_oneOf(group: "eq") + "Match if the field is not equal to the provided value." + ne: Date @fdc_oneOf(group: "ne") + "Match if the field is not equal to the provided CEL expression." + ne_expr: Date_Expr @fdc_oneOf(group: "ne") + "Match if the field is not equal to the provided relative date." + ne_date: Date_Relative @fdc_oneOf(group: "ne") + "Match if the field value is among the provided list of values." + in: [Date!] + "Match if the field value is not among the provided list of values." + nin: [Date!] + "Match if the field value is greater than the provided value." + gt: Date @fdc_oneOf(group: "gt") + "Match if the field value is greater than the provided CEL expression." + gt_expr: Date_Expr @fdc_oneOf(group: "gt") + "Match if the field value is greater than the provided relative date." + gt_date: Date_Relative @fdc_oneOf(group: "gt") + "Match if the field value is greater than or equal to the provided value." + ge: Date @fdc_oneOf(group: "ge") + "Match if the field value is greater than or equal to the provided CEL expression." + ge_expr: Date_Expr @fdc_oneOf(group: "ge") + "Match if the field value is greater than or equal to the provided relative date." + ge_date: Date_Relative @fdc_oneOf(group: "ge") + "Match if the field value is less than the provided value." + lt: Date @fdc_oneOf(group: "lt") + "Match if the field value is less than the provided CEL expression." + lt_expr: Date_Expr @fdc_oneOf(group: "lt") + "Match if the field value is less than the provided relative date." + lt_date: Date_Relative @fdc_oneOf(group: "lt") + "Match if the field value is less than or equal to the provided value." + le: Date @fdc_oneOf(group: "le") + "Match if the field value is less than or equal to the provided CEL expression." + le_expr: Date_Expr @fdc_oneOf(group: "le") + "Match if the field value is less than or equal to the provided relative date." + le_date: Date_Relative @fdc_oneOf(group: "le") +} + +"Conditions on a`Date` list." +input Date_ListFilter { + "Match if the list contains the provided date." + includes: Date @fdc_oneOf(group: "includes") + "Match if the list contains the provided date CEL expression." + includes_expr: Date_Expr @fdc_oneOf(group: "includes") + "Match if the list contains the provided relative date." + includes_date: Date_Relative @fdc_oneOf(group: "includes") + "Match if the list does not contain the provided date." + excludes: Date @fdc_oneOf(group: "excludes") + "Match if the list does not contain the provided date CEL expression." + excludes_expr: Date_Expr @fdc_oneOf(group: "excludes") + "Match if the list does not contain the provided relative date." + excludes_date: Date_Relative @fdc_oneOf(group: "excludes") + "Match if the list contains all the provided dates." + includesAll: [Date!] + "Match if the list contains none of the provided dates." + excludesAll: [Date!] +} + +"Conditions on a `Timestamp` value." +input Timestamp_Filter { + "Match if the field `IS NULL`." + isNull: Boolean + "Match if the field is exactly equal to the provided value." + eq: Timestamp @fdc_oneOf(group: "eq") + "Match if the field equals the provided CEL expression." + eq_expr: Timestamp_Expr @fdc_oneOf(group: "eq") + "Match if the field equals the provided relative time." + eq_time: Timestamp_Relative @fdc_oneOf(group: "eq") + "Match if the field is not equal to the provided value." + ne: Timestamp @fdc_oneOf(group: "ne") + "Match if the field is not equal to the provided CEL expression." + ne_expr: Timestamp_Expr @fdc_oneOf(group: "ne") + "Match if the field is not equal to the provided relative time." + ne_time: Timestamp_Relative @fdc_oneOf(group: "ne") + "Match if the field value is among the provided list of values." + in: [Timestamp!] + "Match if the field value is not among the provided list of values." + nin: [Timestamp!] + "Match if the field value is greater than the provided value." + gt: Timestamp @fdc_oneOf(group: "gt") + "Match if the field value is greater than the provided CEL expression." + gt_expr: Timestamp_Expr @fdc_oneOf(group: "gt") + "Match if the field value is greater than the provided relative time." + gt_time: Timestamp_Relative @fdc_oneOf(group: "gt") + "Match if the field value is greater than or equal to the provided value." + ge: Timestamp @fdc_oneOf(group: "ge") + "Match if the field value is greater than or equal to the provided CEL expression." + ge_expr: Timestamp_Expr @fdc_oneOf(group: "ge") + "Match if the field value is greater than or equal to the provided relative time." + ge_time: Timestamp_Relative @fdc_oneOf(group: "ge") + "Match if the field value is less than the provided value." + lt: Timestamp @fdc_oneOf(group: "lt") + "Match if the field value is less than the provided CEL expression." + lt_expr: Timestamp_Expr @fdc_oneOf(group: "lt") + "Match if the field value is less than the provided relative time." + lt_time: Timestamp_Relative @fdc_oneOf(group: "lt") + "Match if the field value is less than or equal to the provided value." + le: Timestamp @fdc_oneOf(group: "le") + "Match if the field value is less than or equal to the provided CEL expression." + le_expr: Timestamp_Expr @fdc_oneOf(group: "le") + "Match if the field value is less than or equal to the provided relative time." + le_time: Timestamp_Relative @fdc_oneOf(group: "le") +} + +"Conditions on a `Timestamp` list." +input Timestamp_ListFilter { + "Match if the list contains the provided timestamp." + includes: Timestamp @fdc_oneOf(group: "includes") + "Match if the list contains the provided timestamp CEL expression." + includes_expr: Timestamp_Expr @fdc_oneOf(group: "includes") + "Match if the list contains the provided relative timestamp." + includes_time: Timestamp_Relative @fdc_oneOf(group: "includes") + "Match if the list does not contain the provided timestamp." + excludes: Timestamp @fdc_oneOf(group: "excludes") + "Match if the list does not contain the provided timestamp CEL expression." + excludes_expr: Timestamp_Expr @fdc_oneOf(group: "excludes") + "Match if the list does not contain the provided relative timestamp." + excludes_time: Timestamp_Relative @fdc_oneOf(group: "excludes") + "Match if the list contains all the provided timestamps." + includesAll: [Timestamp!] + "Match if the list contains none of the provided timestamps." + excludesAll: [Timestamp!] +} + +"Update input of a `Date` value." +input Date_Update { + "Set the field to the provided date." + set: Date @fdc_oneOf(group: "set") + "Set the field to the provided date CEL expression." + set_expr: Date_Expr @fdc_oneOf(group: "set") + "Set the field to the provided relative date." + set_date: Date_Relative @fdc_oneOf(group: "set") +} + +"Update input of a `Date` list value." +input Date_ListUpdate { + "Replace the current list with the provided list of `Date` values." + set: [Date!] + "Append the provided `Date` values to the existing list." + append: [Date!] + "Prepend the provided `Date` values to the existing list." + prepend: [Date!] + "Remove the date value at the specified index." + delete: Int + "The index of the list to perform updates." + i: Int + "Update the date value at the specified index." + update: Date +} + +"Update input of a `Timestamp` value." +input Timestamp_Update { + "Set the field to the provided timestamp." + set: Timestamp @fdc_oneOf(group: "set") + "Set the field to the provided timestamp CEL expression." + set_expr: Timestamp_Expr @fdc_oneOf(group: "set") + "Set the field to the provided relative timestamp." + set_time: Timestamp_Relative @fdc_oneOf(group: "set") +} + +"Update input of an `Timestamp` list value." +input Timestamp_ListUpdate { + "Replace the current list with the provided list of `Timestamp` values." + set: [Timestamp!] + "Append the provided `Timestamp` values to the existing list." + append: [Timestamp!] + "Prepend the provided `Timestamp` values to the existing list." + prepend: [Timestamp!] + "Remove the timestamp value at the specified index." + delete: Int + "The index of the list to perform updates." + i: Int + "Update the timestamp value at the specified index." + update: Timestamp +} + + +"A runtime-calculated `Timestamp` value relative to `now` or `at`." +input Timestamp_Relative @fdc_forbiddenAsVariableType @fdc_forbiddenAsFieldType { + "Match for the current time." + now: True @fdc_oneOf(group: "from", required: true) + "A specific timestamp for matching." + at: Timestamp @fdc_oneOf(group: "from", required: true) + "Add the provided duration to the base timestamp." + add: Timestamp_Duration + "Subtract the provided duration from the base timestamp." + sub: Timestamp_Duration + "Truncate the timestamp to the provided interval." + truncateTo: Timestamp_Interval +} + +input Timestamp_Duration @fdc_forbiddenAsVariableType @fdc_forbiddenAsFieldType { + "The number of milliseconds for the duration." + milliseconds: Int! = 0 + "The number of seconds for the duration." + seconds: Int! = 0 + "The number of minutes for the duration." + minutes: Int! = 0 + "The number of hours for the duration." + hours: Int! = 0 + "The number of days for the duration." + days: Int! = 0 + "The number of weeks for the duration." + weeks: Int! = 0 + "The number of months for the duration." + months: Int! = 0 + "The number of years for the duration." + years: Int! = 0 +} + +enum Timestamp_Interval @fdc_forbiddenAsFieldType { + "Represents a time interval of one second." + SECOND + "Represents a time interval of one minute." + MINUTE + "Represents a time interval of one hour." + HOUR + "Represents a time interval of one day." + DAY + "Represents a time interval of one week." + WEEK + "Represents a time interval of one month." + MONTH + "Represents a time interval of one year." + YEAR +} + +"A runtime-calculated Date value relative to `today` or `on`." +input Date_Relative @fdc_forbiddenAsVariableType @fdc_forbiddenAsFieldType { + "Match for today’s date." + today: True @fdc_oneOf(group: "from", required: true) + "A specific date for matching." + on: Date @fdc_oneOf(group: "from", required: true) + "Add the provided duration to the base date." + add: Date_Duration + "Subtract the provided duration from the base date." + sub: Date_Duration + "Truncate the date to the provided interval." + truncateTo: Date_Interval +} + +input Date_Duration @fdc_forbiddenAsVariableType @fdc_forbiddenAsFieldType { + "The number of days for the duration." + days: Int! = 0 + "The number of weeks for the duration." + weeks: Int! = 0 + "The number of months for the duration." + months: Int! = 0 + "The number of years for the duration." + years: Int! = 0 +} + +enum Date_Interval @fdc_forbiddenAsFieldType { + "Represents a time interval of one week." + WEEK + "Represents a time interval of one month." + MONTH + "Represents a time interval of one year." + YEAR +} + +"Update input of a `String` value." +input String_Update { + "Set the field to a provided value." + set: String @fdc_oneOf(group: "set") + "Set the field to a provided server value expression." + set_expr: String_Expr @fdc_oneOf(group: "set") +} + +"Update input of a `String` list value." +input String_ListUpdate { + "Set the list with the provided values." + set: [String!] + "Append the provided values to the existing list." + append: [String!] + "Prepend the provided values to the existing list." + prepend: [String!] +} + +"Update input of a `UUID` value." +input UUID_Update { + "Set the field to a provided UUID." + set: UUID @fdc_oneOf(group: "set") + "Set the field to a provided UUID expression." + set_expr: UUID_Expr @fdc_oneOf(group: "set") +} + +"Update input of an `ID` list value." +input UUID_ListUpdate { + "Set the list with the provided list of UUIDs." + set: [UUID!] + "Append the provided UUIDs to the existing list." + append: [UUID!] + "Prepend the provided UUIDs to the existing list." + prepend: [UUID!] +} + +"Update input of an `Int` value." +input Int_Update { + "Set the field to a provided value." + set: Int + "Increment the field by a provided value." + inc: Int + "Decrement the field by a provided value." + dec: Int +} + +"Update input of an `Int` list value." +input Int_ListUpdate { + "Set the list with the provided values." + set: [Int!] + "Append the provided list of values to the existing list." + append: [Int!] + "Prepend the provided list of values to the existing list." + prepend: [Int!] +} + +"Update input of an `Int64` value." +input Int64_Update { + "Set the field to a provided value." + set: Int64 + "Increment the field by a provided value." + inc: Int64 + "Decrement the field by a provided value." + dec: Int64 +} + +"Update input of an `Int64` list value." +input Int64_ListUpdate { + "Replace the list with the provided values." + set: [Int64!] + "Append the provided list of values to the existing list." + append: [Int64!] + "Prepend the provided list of values to the existing list." + prepend: [Int64!] +} + +"Update input of a `Float` value." +input Float_Update { + "Set the field to a provided value." + set: Float + "Increment the field by a provided value." + inc: Float + "Decrement the field by a provided value." + dec: Float +} + +"Update input of a `Float` list value." +input Float_ListUpdate { + "Set the list with the provided values." + set: [Float!] + "Append the provided list of values to the existing list." + append: [Float!] + "Prepend the provided list of values to the existing list." + prepend: [Float!] +} + +"Update input of a `Boolean` value." +input Boolean_Update { + "Set the field to a provided value." + set: Boolean +} + +"Update input of a `Boolean` list value." +input Boolean_ListUpdate { + "Set the list with the provided values." + set: [Boolean!] + "Append the provided list of values to the existing list." + append: [Boolean!] + "Prepend the provided list of values to the existing list." + prepend: [Boolean!] +} + +"Update input of an `Any` value." +input Any_Update { + "Set the field to a provided value." + set: Any +} + +"Update input of an `Any` list value." +input Any_ListUpdate { + "Set the list with the provided values." + set: [Any!] + "Append the provided list of values to the existing list." + append: [Any!] + "Prepend the provided list of values to the existing list." + prepend: [Any!] +} + +type Query { + """ + _service provides customized introspection on Firebase Data Connect Sevice. + """ + _service: _Service! +} + +""" +Vector is an array of single-precision floating-point numbers, serialized +as a JSON array. All elements must be finite (no NaN, Infinity or -Infinity). + +Example: [1.1, 2, 3.3] + +In the PostgreSQL table, it's stored as [`pgvector`](https://github.com/pgvector/pgvector). +""" +scalar Vector + +""" +Defines the similarity function to use when comparing vectors in queries. + +Defaults to `INNER_PRODUCT`. + +View [all vector functions](https://github.com/pgvector/pgvector?tab=readme-ov-file#vector-functions). +""" +enum VectorSimilarityMethod { + "Measures the Euclidean (L2) distance between two vectors." + L2 + "Measures the cosine similarity between two vectors." + COSINE + "Measures the inner product(dot product) between two vectors." + INNER_PRODUCT +} + +"Conditions on a Vector value." +input Vector_Filter { + "Match if the field is exactly equal to the provided vector." + eq: Vector + "Match if the field is not equal to the provided vector." + ne: Vector + "Match if the field value is among the provided list of vectors." + in: [Vector!] + "Match if the field value is not among the provided list of vectors." + nin: [Vector!] + "Match if the field is `NULL`." + isNull: Boolean +} + +input Vector_ListFilter { + "Match if the list includes the supplied vector." + includes: Vector + "Match if the list does not include the supplied vector." + excludes: Vector + "Match if the list contains all the provided vectors." + includesAll: [Vector!] + "Match if the list contains none of the provided vectors." + excludesAll: [Vector!] +} + +"Update input of a Vector value." +input Vector_Update { + "Set the field to the provided vector value." + set: Vector @fdc_oneOf(group: "set") + "Set the field to the vector embedding result from a text input." + set_embed: Vector_Embed @fdc_oneOf(group: "set") +} + + +"Update input of a Vector list value." +input Vector_ListUpdate { + "Replace the current list with the provided list of Vector values." + set: [Vector] + "Append the provided Vector values to the existing list." + append: [Vector] + "Prepend the provided Vector values to the existing list." + prepend: [Vector] + "Delete the vector at the specified index." + delete: Int + "The index of the vector to be updated." + i: Int + "Update the vector at the specified index." + update: Vector +} + +""" +Create a vector embedding of text using the given model on Vertex AI. + +Cloud SQL for Postgresql natively integrates with [Vertex AI Text embeddings API](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/text-embeddings-api) +to effectively generate text embeddings. + +If you uses [`Vector`](scalar.md#Vector) in your schema, Firebase Data Connect automatically installs +[`pgvector`](https://github.com/pgvector/pgvector) and [`google_ml_integration`](https://cloud.google.com/sql/docs/postgres/integrate-cloud-sql-with-vertex-ai) +Postgres extensions in your Cloud SQL database. + +Given a Post table with a `Vector` embedding field. + +```graphql +type Post @table { + content: String! + contentEmbedding: Vector @col(size:768) +} +``` + +###### Example: Insert embedding + +```graphql +mutation CreatePost($content: String!) { + post_insert(data: { + content: $content, + contentEmbedding_embed: {model: "textembedding-gecko@003", text: $content}, + }) +} +``` + +###### Example: Vector similarity Search + +```graphql +query SearchPost($query: String!) { + posts_contentEmbedding_similarity(compare_embed: {model: "textembedding-gecko@003", text: $query}) { + id + content + } +} +``` +""" +input Vector_Embed @fdc_forbiddenAsVariableType { + """ + The model to use for vector embedding. + Recommend the latest stable model: `textembedding-gecko@003`. + """ + model: Vector_Embed_Model! + "The text to generate the vector embedding from." + text: String! +} + +""" +The Vertex AI model version that is required in input `Vector_Embed`. + +It is recommended to use the latest stable model version: `textembedding-gecko@003`. + +View all supported [Vertex AI Text embeddings APIs](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/text-embeddings-api). +""" +scalar Vector_Embed_Model + @specifiedBy(url: "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/model-versioning") + @fdc_forbiddenAsVariableType + @fdc_forbiddenAsFieldType + @fdc_example(value: "textembedding-gecko@003", description: "A stable version of the textembedding-gecko model") + @fdc_example(value: "textembedding-gecko@001", description: "An older version of the textembedding-gecko model") + @fdc_example(value: "text-embedding-004", description: "Annother textembedding model") + diff --git a/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/connector/mutations.gql b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/connector/mutations.gql index 1e4194f7999b..879ed8c7e404 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/connector/mutations.gql +++ b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/connector/mutations.gql @@ -7,15 +7,16 @@ mutation addDirectorToMovie($personId: Person_Key, $movieId: UUID) directedBy_insert(data: { directedby: $personId, movieId: $movieId }) } mutation addTimestamp($timestamp: Timestamp!) @auth(level: PUBLIC) { - timestampHolder_insert(data: { - timestamp: $timestamp - }) + timestampHolder_insert(data: { timestamp: $timestamp }) } -mutation addDateAndTimestamp($date: Date!, $timestamp: Timestamp!) @auth(level: PUBLIC) { - timestampHolder_insert(data: { - date: $date, - timestamp: $timestamp - }) + +mutation addDateAndTimestamp($date: Date!, $timestamp: Timestamp!) +@auth(level: PUBLIC) { + timestampHolder_insert(data: { date: $date, timestamp: $timestamp }) +} + +mutation deleteAllTimestamps @auth(level: PUBLIC) { + timestampHolder_deleteMany(all: true) } mutation seedMovies @auth(level: PUBLIC) { @@ -63,9 +64,7 @@ mutation deleteMovie($id: UUID!) @auth(level: PUBLIC) { } mutation thing($title: Any! = "ABC") @auth(level: PUBLIC) { - thing_insert(data: { - title: $title - }) + thing_insert(data: { title: $title }) } mutation seedData @auth(level: PUBLIC) { diff --git a/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/connector/queries.gql b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/connector/queries.gql index 3c1ce8191a13..d0769d53a329 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/connector/queries.gql +++ b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/connector/queries.gql @@ -3,6 +3,8 @@ query ListMovies @auth(level: USER) { movies { id title + genre + releaseYear directed_by: people_via_DirectedBy { name } @@ -22,27 +24,43 @@ query ListMoviesByPartialTitle($input: String!) @auth(level: PUBLIC) { query ListPersons @auth(level: USER) { people { - id + id name } } query ListThing($data: Any) @auth(level: USER) { - things(where: { - title: { - eq: $data - } - }) { + things(where: { title: { eq: $data } }) { title } } query ListTimestamps @auth(level: USER) { timestampHolders { - timestamp, + timestamp date } } +query ListMoviesByGenre($genre: String! = "Action") @auth(level: PUBLIC) { + mostPopular: movies( + where: { genre: { eq: $genre } } + orderBy: { rating: DESC } + limit: 10 + ) { + id + title + rating + } + mostRecent: movies( + where: { genre: { eq: $genre } } + orderBy: { releaseYear: DESC } + ) { + id + title + rating + } +} + # List subset of fields for users # query ListUsers @auth(level: PUBLIC) { # users { diff --git a/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/schema/schema.gql b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/schema/schema.gql index 5dac9add4885..2a51ef78d160 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/schema/schema.gql +++ b/packages/firebase_data_connect/firebase_data_connect/example/dataconnect/schema/schema.gql @@ -15,7 +15,7 @@ type Movie @table { type Thing @table { title: Any! } -type TimestampHolder @table { +type TimestampHolder @table(key: ["timestamp"]) { timestamp: Timestamp! date: Date } diff --git a/packages/firebase_data_connect/firebase_data_connect/example/integration_test/generation_e2e.dart b/packages/firebase_data_connect/firebase_data_connect/example/integration_test/generation_e2e.dart index ccedb69f4930..d30191556e7e 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/integration_test/generation_e2e.dart +++ b/packages/firebase_data_connect/firebase_data_connect/example/integration_test/generation_e2e.dart @@ -64,6 +64,22 @@ void runGenerationTest() { expect(ref, isNotNull); expect(ref.execute, isNotNull); }); + + testWidgets( + 'should populate only non-required fields with builder pattern', + (WidgetTester tester) async { + final movieBuilder = MoviesConnector.instance.createMovie( + genre: 'Action', + title: 'Inception', + releaseYear: 2010, + ); + + movieBuilder.rating(4.8); + + final mutationRef = movieBuilder.ref(); + expect(mutationRef, isNotNull); + expect(mutationRef.execute, isNotNull); + }); }, ); } diff --git a/packages/firebase_data_connect/firebase_data_connect/example/integration_test/query_e2e.dart b/packages/firebase_data_connect/firebase_data_connect/example/integration_test/query_e2e.dart index 4ef24e41b467..daff17830029 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/integration_test/query_e2e.dart +++ b/packages/firebase_data_connect/firebase_data_connect/example/integration_test/query_e2e.dart @@ -14,6 +14,10 @@ Future deleteAllMovies() async { } } +Future deleteAllTimestamps() async { + await MoviesConnector.instance.deleteAllTimestamps().ref().execute(); +} + void runQueryTests() { group( '$FirebaseDataConnect.instance query', @@ -128,6 +132,106 @@ void runQueryTests() { final result2 = value2.data; expect(result2.movies.length, 0); }); + + testWidgets('should be able to ignore optional values', + (WidgetTester tester) async { + MutationRef ref = MoviesConnector.instance + .createMovie( + genre: 'Action', + title: 'The Matrix', + releaseYear: 1999, + ) + .ref(); + + await ref.execute(); + + MutationRef ref2 = MoviesConnector.instance + .createMovie( + genre: 'SF', + title: 'Star Wars', + releaseYear: 1977, + ) + .ref(); + await ref2.execute(); + + final value = + await MoviesConnector.instance.listMoviesByGenre().ref().execute(); + final result = value.data; + expect(result.mostPopular.length, 1); + expect(result.mostPopular[0].title, 'The Matrix'); + + final value2 = await MoviesConnector.instance + .listMoviesByGenre() + .genre('SF') + .ref() + .execute(); + + final result2 = value2.data; + expect(result2.mostPopular.length, 1); + expect(result2.mostPopular[0].title, 'Star Wars'); + }); }, ); + + group('timestamp into query', () { + testWidgets('should be able to add a timestamp', + (WidgetTester tester) async { + await deleteAllTimestamps(); + final timestamp = Timestamp.fromJson('1970-01-11T00:00:00Z'); + MutationRef ref = + MoviesConnector.instance.addTimestamp(timestamp: timestamp).ref(); + + await ref.execute(); + + final value = + await MoviesConnector.instance.listTimestamps().ref().execute(); + final result = value.data; + expect(result.timestampHolders.length, 1); + expect(result.timestampHolders[0].timestamp.seconds, timestamp.seconds); + expect(result.timestampHolders[0].timestamp.nanoseconds, + timestamp.nanoseconds); + }); + + testWidgets('should be able to add a date and a timestamp', + (WidgetTester tester) async { + await deleteAllTimestamps(); + final timestamp = Timestamp.fromJson('1970-01-11T00:00:00Z'); + final date = DateTime(1970, 1, 11); + MutationRef ref = MoviesConnector.instance + .addDateAndTimestamp(date: date, timestamp: timestamp) + .ref(); + + await ref.execute(); + + final value = + await MoviesConnector.instance.listTimestamps().ref().execute(); + final result = value.data; + expect(result.timestampHolders.length, 1); + expect(result.timestampHolders[0].timestamp.seconds, timestamp.seconds); + expect(result.timestampHolders[0].timestamp.nanoseconds, + timestamp.nanoseconds); + expect(result.timestampHolders[0].date, date); + }); + + testWidgets('should only send fields set in the builder pattern', + (WidgetTester tester) async { + await deleteAllMovies(); + final movieBuilder = MoviesConnector.instance.createMovie( + genre: 'Action', // Required field + title: 'Inception', // Required field + releaseYear: 2010, // Required field + ); + + await movieBuilder.ref().execute(); + + final value = await MoviesConnector.instance.listMovies().ref().execute(); + + final result = value.data; + expect(result.movies.length, 1); + expect(result.movies[0].title, 'Inception'); + expect(result.movies[0].genre, 'Action'); + expect(result.movies[0].releaseYear, 2010); + expect(result.movies[0].rating, null); + }); + }); } diff --git a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/add_date_and_timestamp.dart b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/add_date_and_timestamp.dart index a47e74d62b5b..53e79a0bb44e 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/add_date_and_timestamp.dart +++ b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/add_date_and_timestamp.dart @@ -32,21 +32,21 @@ class AddDateAndTimestampVariablesBuilder { } class AddDateAndTimestampTimestampHolderInsert { - String id; + Timestamp timestamp; AddDateAndTimestampTimestampHolderInsert.fromJson(dynamic json) - : id = nativeFromJson(json['id']) {} + : timestamp = Timestamp.fromJson(json['timestamp']) {} Map toJson() { Map json = {}; - json['id'] = nativeToJson(id); + json['timestamp'] = timestamp.toJson(); return json; } AddDateAndTimestampTimestampHolderInsert({ - required this.id, + required this.timestamp, }); } diff --git a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/add_timestamp.dart b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/add_timestamp.dart index 9a5f4266026e..685c15c2e358 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/add_timestamp.dart +++ b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/add_timestamp.dart @@ -28,21 +28,21 @@ class AddTimestampVariablesBuilder { } class AddTimestampTimestampHolderInsert { - String id; + Timestamp timestamp; AddTimestampTimestampHolderInsert.fromJson(dynamic json) - : id = nativeFromJson(json['id']) {} + : timestamp = Timestamp.fromJson(json['timestamp']) {} Map toJson() { Map json = {}; - json['id'] = nativeToJson(id); + json['timestamp'] = timestamp.toJson(); return json; } AddTimestampTimestampHolderInsert({ - required this.id, + required this.timestamp, }); } diff --git a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/delete_all_timestamps.dart b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/delete_all_timestamps.dart new file mode 100644 index 000000000000..182ac579e9b1 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/delete_all_timestamps.dart @@ -0,0 +1,41 @@ +part of movies; + +class DeleteAllTimestampsVariablesBuilder { + FirebaseDataConnect _dataConnect; + + DeleteAllTimestampsVariablesBuilder( + this._dataConnect, + ); + Deserializer dataDeserializer = + (dynamic json) => DeleteAllTimestampsData.fromJson(jsonDecode(json)); + + Future> execute() { + return this.ref().execute(); + } + + MutationRef ref() { + return _dataConnect.mutation( + "deleteAllTimestamps", dataDeserializer, emptySerializer, null); + } +} + +class DeleteAllTimestampsData { + int timestampHolder_deleteMany; + + DeleteAllTimestampsData.fromJson(dynamic json) + : timestampHolder_deleteMany = + nativeFromJson(json['timestampHolder_deleteMany']) {} + + Map toJson() { + Map json = {}; + + json['timestampHolder_deleteMany'] = + nativeToJson(timestampHolder_deleteMany); + + return json; + } + + DeleteAllTimestampsData({ + required this.timestampHolder_deleteMany, + }); +} diff --git a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/list_movies.dart b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/list_movies.dart index 170559b9a690..18ff51f45bcb 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/list_movies.dart +++ b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/list_movies.dart @@ -24,6 +24,10 @@ class ListMoviesMovies { String title; + String genre; + + int? releaseYear; + List directed_by; double? rating; @@ -31,6 +35,10 @@ class ListMoviesMovies { ListMoviesMovies.fromJson(dynamic json) : id = nativeFromJson(json['id']), title = nativeFromJson(json['title']), + genre = nativeFromJson(json['genre']), + releaseYear = json['releaseYear'] == null + ? null + : nativeFromJson(json['releaseYear']), directed_by = (json['directed_by'] as List) .map((e) => ListMoviesMoviesDirectedBy.fromJson(e)) .toList(), @@ -45,6 +53,12 @@ class ListMoviesMovies { json['title'] = nativeToJson(title); + json['genre'] = nativeToJson(genre); + + if (releaseYear != null) { + json['releaseYear'] = nativeToJson(releaseYear); + } + json['directed_by'] = directed_by.map((e) => e.toJson()).toList(); if (rating != null) { @@ -57,6 +71,8 @@ class ListMoviesMovies { ListMoviesMovies({ required this.id, required this.title, + required this.genre, + this.releaseYear, required this.directed_by, this.rating, }); diff --git a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/list_movies_by_genre.dart b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/list_movies_by_genre.dart new file mode 100644 index 000000000000..69d0bd8664f4 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/list_movies_by_genre.dart @@ -0,0 +1,155 @@ +part of movies; + +class ListMoviesByGenreVariablesBuilder { + Optional _genre = Optional.optional(nativeFromJson, nativeToJson); + + FirebaseDataConnect _dataConnect; + ListMoviesByGenreVariablesBuilder genre(String t) { + this._genre.value = t; + return this; + } + + ListMoviesByGenreVariablesBuilder( + this._dataConnect, + ); + Deserializer dataDeserializer = + (dynamic json) => ListMoviesByGenreData.fromJson(jsonDecode(json)); + Serializer varsSerializer = + (ListMoviesByGenreVariables vars) => jsonEncode(vars.toJson()); + Future> + execute() { + return this.ref().execute(); + } + + QueryRef ref() { + ListMoviesByGenreVariables vars = ListMoviesByGenreVariables( + genre: _genre, + ); + + return _dataConnect.query( + "ListMoviesByGenre", dataDeserializer, varsSerializer, vars); + } +} + +class ListMoviesByGenreMostPopular { + String id; + + String title; + + double? rating; + + ListMoviesByGenreMostPopular.fromJson(dynamic json) + : id = nativeFromJson(json['id']), + title = nativeFromJson(json['title']), + rating = json['rating'] == null + ? null + : nativeFromJson(json['rating']) {} + + Map toJson() { + Map json = {}; + + json['id'] = nativeToJson(id); + + json['title'] = nativeToJson(title); + + if (rating != null) { + json['rating'] = nativeToJson(rating); + } + + return json; + } + + ListMoviesByGenreMostPopular({ + required this.id, + required this.title, + this.rating, + }); +} + +class ListMoviesByGenreMostRecent { + String id; + + String title; + + double? rating; + + ListMoviesByGenreMostRecent.fromJson(dynamic json) + : id = nativeFromJson(json['id']), + title = nativeFromJson(json['title']), + rating = json['rating'] == null + ? null + : nativeFromJson(json['rating']) {} + + Map toJson() { + Map json = {}; + + json['id'] = nativeToJson(id); + + json['title'] = nativeToJson(title); + + if (rating != null) { + json['rating'] = nativeToJson(rating); + } + + return json; + } + + ListMoviesByGenreMostRecent({ + required this.id, + required this.title, + this.rating, + }); +} + +class ListMoviesByGenreData { + List mostPopular; + + List mostRecent; + + ListMoviesByGenreData.fromJson(dynamic json) + : mostPopular = (json['mostPopular'] as List) + .map((e) => ListMoviesByGenreMostPopular.fromJson(e)) + .toList(), + mostRecent = (json['mostRecent'] as List) + .map((e) => ListMoviesByGenreMostRecent.fromJson(e)) + .toList() {} + + Map toJson() { + Map json = {}; + + json['mostPopular'] = mostPopular.map((e) => e.toJson()).toList(); + + json['mostRecent'] = mostRecent.map((e) => e.toJson()).toList(); + + return json; + } + + ListMoviesByGenreData({ + required this.mostPopular, + required this.mostRecent, + }); +} + +class ListMoviesByGenreVariables { + late Optional genre; + + ListMoviesByGenreVariables.fromJson(Map json) { + genre = Optional.optional(nativeFromJson, nativeToJson); + genre.value = + json['genre'] == null ? null : nativeFromJson(json['genre']); + } + + Map toJson() { + Map json = {}; + + if (genre.state == OptionalState.set) { + json['genre'] = genre.toJson(); + } + + return json; + } + + ListMoviesByGenreVariables({ + required this.genre, + }); +} diff --git a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/movies.dart b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/movies.dart index b936ec498fc1..e2151b9e2d89 100644 --- a/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/movies.dart +++ b/packages/firebase_data_connect/firebase_data_connect/example/lib/generated/movies.dart @@ -11,6 +11,8 @@ part 'add_timestamp.dart'; part 'add_date_and_timestamp.dart'; +part 'delete_all_timestamps.dart'; + part 'seed_movies.dart'; part 'create_movie.dart'; @@ -31,6 +33,8 @@ part 'list_thing.dart'; part 'list_timestamps.dart'; +part 'list_movies_by_genre.dart'; + class MoviesConnector { AddPersonVariablesBuilder addPerson() { return AddPersonVariablesBuilder( @@ -64,6 +68,12 @@ class MoviesConnector { ); } + DeleteAllTimestampsVariablesBuilder deleteAllTimestamps() { + return DeleteAllTimestampsVariablesBuilder( + dataConnect, + ); + } + SeedMoviesVariablesBuilder seedMovies() { return SeedMoviesVariablesBuilder( dataConnect, @@ -137,6 +147,12 @@ class MoviesConnector { ); } + ListMoviesByGenreVariablesBuilder listMoviesByGenre() { + return ListMoviesByGenreVariablesBuilder( + dataConnect, + ); + } + static ConnectorConfig connectorConfig = ConnectorConfig( 'us-west2', 'movies', diff --git a/packages/firebase_data_connect/firebase_data_connect/lib/src/core/ref.dart b/packages/firebase_data_connect/firebase_data_connect/lib/src/core/ref.dart index 68f528ceff78..00fbfd704f08 100644 --- a/packages/firebase_data_connect/firebase_data_connect/lib/src/core/ref.dart +++ b/packages/firebase_data_connect/firebase_data_connect/lib/src/core/ref.dart @@ -161,7 +161,7 @@ class QueryRef extends OperationRef { .cast>(); if (_queryManager.containsQuery(operationName, variables, varsSerialized)) { try { - this.execute(); + unawaited(this.execute()); } catch (_) { // Call to `execute` should properly pass the error to the Stream. log("Error thrown by execute. The error will propagate via onError."); diff --git a/packages/firebase_data_connect/firebase_data_connect/test/src/core/ref_test.dart b/packages/firebase_data_connect/firebase_data_connect/test/src/core/ref_test.dart index d6a66907499d..2e4417c72439 100644 --- a/packages/firebase_data_connect/firebase_data_connect/test/src/core/ref_test.dart +++ b/packages/firebase_data_connect/firebase_data_connect/test/src/core/ref_test.dart @@ -15,7 +15,6 @@ // ignore_for_file: unused_local_variable import 'dart:async'; -import 'package:http/http.dart' as http; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_data_connect/firebase_data_connect.dart'; @@ -23,12 +22,12 @@ import 'package:firebase_data_connect/src/common/common_library.dart'; import 'package:firebase_data_connect/src/core/ref.dart'; import 'package:firebase_data_connect/src/network/rest_library.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import '../network/rest_transport_test.mocks.dart'; - -// Mock classes -class MockDataConnectTransport extends Mock implements DataConnectTransport {} +import 'ref_test.mocks.dart'; class MockFirebaseDataConnect extends Mock implements FirebaseDataConnect {} @@ -51,6 +50,14 @@ class MockQueryManager extends Mock implements QueryManager {} class MockOperationRef extends Mock implements OperationRef {} +class MockQueryRef extends Mock + implements QueryRef {} + +class MockStreamController extends Mock implements StreamController {} + +@GenerateMocks([ + DataConnectTransport, +]) void main() { group('OperationResult', () { test('should initialize correctly with provided data and ref', () { @@ -101,6 +108,138 @@ void main() { expect(queryManager.trackedQueries['testQuery']!['varsAsStr'], isNotNull); expect(stream, isA()); }); + + test('addQuery should return existing StreamController if query exists', + () { + final stream1 = + queryManager.addQuery('testQuery', 'variables', 'varsAsStr'); + final stream2 = + queryManager.addQuery('testQuery', 'variables', 'varsAsStr'); + + expect(stream1, stream2); + }); + + test( + 'subscribe should propagate errors via the stream', + () async { + final mockTransport = MockDataConnectTransport(); + final queryManager = QueryManager(mockDataConnect); + final mockStreamController = MockStreamController(); + final completer = Completer(); + + final streamController = StreamController.broadcast(); + + queryManager.trackedQueries['testQuery'] = {'': streamController}; + + final queryRef = QueryRef( + mockDataConnect, + 'testQuery', + mockTransport, + emptySerializer, + queryManager, + emptySerializer, + null, + ); + + streamController.stream.listen( + (event) { + fail('Error was not propagated to the stream'); + }, + onError: (error) { + expect(error, isA()); + expect(error.toString(), contains('Test Error')); + completer.complete(); + }, + ); + + // Manually trigger the callback since subscribe is not working in unit tests + // TODO(Lyokone): find a way of using subscribe in unit tests + queryManager.triggerCallback( + 'testQuery', '', queryRef, null, Exception('Test Error')); + + await completer.future.timeout(Duration(seconds: 2), onTimeout: () { + fail('Error was not propagated to the stream'); + }); + }, + ); + + test( + 'should propagate data via the stream', + () async { + final mockTransport = MockDataConnectTransport(); + final queryManager = QueryManager(mockDataConnect); + final mockStreamController = MockStreamController(); + final completer = Completer(); + + when(mockTransport.invokeQuery('testQuery', any, any, null, null)) + .thenAnswer((_) async => 'Deserialized Data'); + + final streamController = StreamController.broadcast(); + + queryManager.trackedQueries['testQuery'] = {'': streamController}; + + final queryRef = QueryRef( + mockDataConnect, + 'testQuery', + mockTransport, + emptySerializer, + queryManager, + emptySerializer, + null, + ); + + streamController.stream.listen( + (event) { + expect(event.data, 'Deserialized Data'); + expect(event.ref, queryRef); + expect(event.dataConnect, mockDataConnect); + completer.complete(); + }, + onError: (error) { + fail('Error was propagated to the stream'); + }, + ); + + // Manually trigger the callback since subscribe is not working in unit tests + // TODO(Lyokone): find a way of using subscribe in unit tests + queryManager.triggerCallback( + 'testQuery', '', queryRef, 'Deserialized Data', null); + + await completer.future.timeout(Duration(seconds: 2), onTimeout: () { + fail('Data was not propagated to the stream'); + }); + }, + ); + + test( + 'execute should propagate error as string when server responds with error', + () async { + final mockTransport = MockDataConnectTransport(); + final queryManager = QueryManager(mockDataConnect); + + // Simulate server throwing an exception + when(mockTransport.invokeQuery(any, any, any, any, null)) + .thenThrow(Exception('Server Error')); + + final queryRef = QueryRef( + mockDataConnect, + 'testQuery', + mockTransport, + emptySerializer, + queryManager, + emptySerializer, + 'variables', + ); + + try { + await queryRef.execute(); + fail('Expected execute to throw an exception.'); + } catch (error) { + expect(error, isA()); + expect(error.toString(), contains('Server Error')); + } + }, + ); }); group('MutationRef', () { diff --git a/packages/firebase_data_connect/firebase_data_connect/test/src/core/ref_test.mocks.dart b/packages/firebase_data_connect/firebase_data_connect/test/src/core/ref_test.mocks.dart new file mode 100644 index 000000000000..4983386e4ce6 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/test/src/core/ref_test.mocks.dart @@ -0,0 +1,245 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in firebase_data_connect/test/src/core/ref_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i3; + +import 'package:firebase_app_check/firebase_app_check.dart' as _i4; +import 'package:firebase_data_connect/src/common/common_library.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i5; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeTransportOptions_0 extends _i1.SmartFake + implements _i2.TransportOptions { + _FakeTransportOptions_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeDataConnectOptions_1 extends _i1.SmartFake + implements _i2.DataConnectOptions { + _FakeDataConnectOptions_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeFuture_2 extends _i1.SmartFake implements _i3.Future { + _FakeFuture_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [DataConnectTransport]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockDataConnectTransport extends _i1.Mock + implements _i2.DataConnectTransport { + MockDataConnectTransport() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.TransportOptions get transportOptions => (super.noSuchMethod( + Invocation.getter(#transportOptions), + returnValue: _FakeTransportOptions_0( + this, + Invocation.getter(#transportOptions), + ), + ) as _i2.TransportOptions); + + @override + set transportOptions(_i2.TransportOptions? _transportOptions) => + super.noSuchMethod( + Invocation.setter( + #transportOptions, + _transportOptions, + ), + returnValueForMissingStub: null, + ); + + @override + _i2.DataConnectOptions get options => (super.noSuchMethod( + Invocation.getter(#options), + returnValue: _FakeDataConnectOptions_1( + this, + Invocation.getter(#options), + ), + ) as _i2.DataConnectOptions); + + @override + set options(_i2.DataConnectOptions? _options) => super.noSuchMethod( + Invocation.setter( + #options, + _options, + ), + returnValueForMissingStub: null, + ); + + @override + set appCheck(_i4.FirebaseAppCheck? _appCheck) => super.noSuchMethod( + Invocation.setter( + #appCheck, + _appCheck, + ), + returnValueForMissingStub: null, + ); + + @override + _i2.CallerSDKType get sdkType => (super.noSuchMethod( + Invocation.getter(#sdkType), + returnValue: _i2.CallerSDKType.core, + ) as _i2.CallerSDKType); + + @override + set sdkType(_i2.CallerSDKType? _sdkType) => super.noSuchMethod( + Invocation.setter( + #sdkType, + _sdkType, + ), + returnValueForMissingStub: null, + ); + + @override + String get appId => (super.noSuchMethod( + Invocation.getter(#appId), + returnValue: _i5.dummyValue( + this, + Invocation.getter(#appId), + ), + ) as String); + + @override + set appId(String? _appId) => super.noSuchMethod( + Invocation.setter( + #appId, + _appId, + ), + returnValueForMissingStub: null, + ); + + @override + _i3.Future invokeQuery( + String? queryName, + _i2.Deserializer? deserializer, + _i2.Serializer? serializer, + Variables? vars, + String? token, + ) => + (super.noSuchMethod( + Invocation.method( + #invokeQuery, + [ + queryName, + deserializer, + serializer, + vars, + token, + ], + ), + returnValue: _i5.ifNotNull( + _i5.dummyValueOrNull( + this, + Invocation.method( + #invokeQuery, + [ + queryName, + deserializer, + serializer, + vars, + token, + ], + ), + ), + (Data v) => _i3.Future.value(v), + ) ?? + _FakeFuture_2( + this, + Invocation.method( + #invokeQuery, + [ + queryName, + deserializer, + serializer, + vars, + token, + ], + ), + ), + ) as _i3.Future); + + @override + _i3.Future invokeMutation( + String? queryName, + _i2.Deserializer? deserializer, + _i2.Serializer? serializer, + Variables? vars, + String? token, + ) => + (super.noSuchMethod( + Invocation.method( + #invokeMutation, + [ + queryName, + deserializer, + serializer, + vars, + token, + ], + ), + returnValue: _i5.ifNotNull( + _i5.dummyValueOrNull( + this, + Invocation.method( + #invokeMutation, + [ + queryName, + deserializer, + serializer, + vars, + token, + ], + ), + ), + (Data v) => _i3.Future.value(v), + ) ?? + _FakeFuture_2( + this, + Invocation.method( + #invokeMutation, + [ + queryName, + deserializer, + serializer, + vars, + token, + ], + ), + ), + ) as _i3.Future); +} diff --git a/packages/firebase_data_connect/firebase_data_connect/test/src/firebase_data_connect_test.mocks.dart b/packages/firebase_data_connect/firebase_data_connect/test/src/firebase_data_connect_test.mocks.dart index 228dc815abe0..ef96fe7c1e26 100644 --- a/packages/firebase_data_connect/firebase_data_connect/test/src/firebase_data_connect_test.mocks.dart +++ b/packages/firebase_data_connect/firebase_data_connect/test/src/firebase_data_connect_test.mocks.dart @@ -1,17 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - // Mocks generated by Mockito 5.4.4 from annotations // in firebase_data_connect/test/src/firebase_data_connect_test.dart. // Do not manually edit this file. diff --git a/packages/firebase_data_connect/firebase_data_connect/test/src/network/grpc_transport_test.dart b/packages/firebase_data_connect/firebase_data_connect/test/src/network/grpc_transport_test.dart new file mode 100644 index 000000000000..452a58a0380e --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/test/src/network/grpc_transport_test.dart @@ -0,0 +1,145 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import 'package:firebase_app_check/firebase_app_check.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_data_connect/src/common/common_library.dart'; +import 'package:firebase_data_connect/src/generated/connector_service.pbgrpc.dart'; +import 'package:firebase_data_connect/src/network/grpc_library.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:grpc/grpc.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; + +import 'grpc_transport_test.mocks.dart'; + +class MockFirebaseAuth extends Mock implements FirebaseAuth {} + +@GenerateMocks([ + ClientChannel, + FirebaseAppCheck, + User, + ConnectorServiceClient, + ResponseFuture +]) +void main() { + late GRPCTransport transport; + late MockFirebaseAuth mockAuth; + late MockFirebaseAppCheck mockAppCheck; + late MockUser mockUser; + late MockConnectorServiceClient mockStub; + + setUp(() { + mockAuth = MockFirebaseAuth(); + mockAppCheck = MockFirebaseAppCheck(); + mockUser = MockUser(); + mockStub = MockConnectorServiceClient(); + + when(mockAuth.currentUser).thenReturn(mockUser); + + transport = GRPCTransport( + TransportOptions('testhost', 443, true), + DataConnectOptions( + 'testProject', + 'testLocation', + 'testConnector', + 'testService', + ), + 'testAppId', + CallerSDKType.core, + mockAppCheck, + ); + + transport.stub = mockStub; + }); + + group('GRPCTransport', () { + test('should correctly initialize secure GRPC channel', () { + expect( + transport.channel.options.credentials, + const ChannelCredentials.secure(), + ); + }); + + test('should correctly initialize insecure GRPC channel', () { + final insecureTransport = GRPCTransport( + TransportOptions('testhost', 443, false), + DataConnectOptions( + 'testProject', + 'testLocation', + 'testConnector', + 'testService', + ), + 'testAppId', + CallerSDKType.core, + mockAppCheck, + ); + + expect( + insecureTransport.channel.options.credentials, + const ChannelCredentials.insecure(), + ); + }); + + test('invokeQuery should throw an error on failed query execution', + () async { + when(mockStub.executeQuery(any, options: anyNamed('options'))) + .thenThrow(Exception('GRPC error')); + + final deserializer = (String data) => 'Deserialized Data'; + + expect( + () => + transport.invokeQuery('testQuery', deserializer, null, null, null), + throwsA(isA()), + ); + }); + + test('invokeMutation should throw an error on failed mutation execution', + () async { + when(mockStub.executeMutation(any, options: anyNamed('options'))) + .thenThrow(Exception('GRPC error')); + + final deserializer = (String data) => 'Deserialized Data'; + + expect( + () => transport.invokeMutation( + 'testMutation', deserializer, null, null, null), + throwsA(isA()), + ); + }); + + test('getMetadata should include auth and appCheck tokens in metadata', + () async { + when(mockUser.getIdToken()).thenAnswer((_) async => 'authToken123'); + when(mockAppCheck.getToken()).thenAnswer((_) async => 'appCheckToken123'); + + final metadata = await transport.getMetadata('authToken123'); + + expect(metadata['x-firebase-auth-token'], 'authToken123'); + expect(metadata['X-Firebase-AppCheck'], 'appCheckToken123'); + }); + + test( + 'getMetadata should handle missing auth and appCheck tokens gracefully', + () async { + when(mockUser.getIdToken()).thenThrow(Exception('Auth error')); + when(mockAppCheck.getToken()).thenThrow(Exception('AppCheck error')); + + final metadata = await transport.getMetadata(null); + + expect(metadata.containsKey('x-firebase-auth-token'), isFalse); + expect(metadata.containsKey('X-Firebase-AppCheck'), isFalse); + }); + }); +} diff --git a/packages/firebase_data_connect/firebase_data_connect/test/src/network/grpc_transport_test.mocks.dart b/packages/firebase_data_connect/firebase_data_connect/test/src/network/grpc_transport_test.mocks.dart new file mode 100644 index 000000000000..48ec8af42b40 --- /dev/null +++ b/packages/firebase_data_connect/firebase_data_connect/test/src/network/grpc_transport_test.mocks.dart @@ -0,0 +1,1062 @@ +// Mocks generated by Mockito 5.4.4 from annotations +// in firebase_data_connect/test/src/network/grpc_transport_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i9; + +import 'package:firebase_app_check/firebase_app_check.dart' as _i12; +import 'package:firebase_app_check_platform_interface/firebase_app_check_platform_interface.dart' + as _i13; +import 'package:firebase_auth/firebase_auth.dart' as _i7; +import 'package:firebase_auth_platform_interface/firebase_auth_platform_interface.dart' + as _i6; +import 'package:firebase_core/firebase_core.dart' as _i5; +import 'package:firebase_data_connect/src/generated/connector_service.pb.dart' + as _i16; +import 'package:firebase_data_connect/src/generated/connector_service.pbgrpc.dart' + as _i15; +import 'package:grpc/service_api.dart' as _i8; +import 'package:grpc/src/client/call.dart' as _i4; +import 'package:grpc/src/client/connection.dart' as _i3; +import 'package:grpc/src/client/http2_channel.dart' as _i10; +import 'package:grpc/src/client/method.dart' as _i11; +import 'package:grpc/src/client/options.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; +import 'package:mockito/src/dummies.dart' as _i14; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeObject_0 extends _i1.SmartFake implements Object { + _FakeObject_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeChannelOptions_1 extends _i1.SmartFake + implements _i2.ChannelOptions { + _FakeChannelOptions_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeClientConnection_2 extends _i1.SmartFake + implements _i3.ClientConnection { + _FakeClientConnection_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeClientCall_3 extends _i1.SmartFake + implements _i4.ClientCall { + _FakeClientCall_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeFirebaseApp_4 extends _i1.SmartFake implements _i5.FirebaseApp { + _FakeFirebaseApp_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeUserMetadata_5 extends _i1.SmartFake implements _i6.UserMetadata { + _FakeUserMetadata_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeMultiFactor_6 extends _i1.SmartFake implements _i7.MultiFactor { + _FakeMultiFactor_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeIdTokenResult_7 extends _i1.SmartFake implements _i6.IdTokenResult { + _FakeIdTokenResult_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeUserCredential_8 extends _i1.SmartFake + implements _i7.UserCredential { + _FakeUserCredential_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeConfirmationResult_9 extends _i1.SmartFake + implements _i7.ConfirmationResult { + _FakeConfirmationResult_9( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeUser_10 extends _i1.SmartFake implements _i7.User { + _FakeUser_10( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeResponseFuture_11 extends _i1.SmartFake + implements _i8.ResponseFuture { + _FakeResponseFuture_11( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeResponseStream_12 extends _i1.SmartFake + implements _i8.ResponseStream { + _FakeResponseStream_12( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeFuture_13 extends _i1.SmartFake implements _i9.Future { + _FakeFuture_13( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [ClientChannel]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockClientChannel extends _i1.Mock implements _i10.ClientChannel { + MockClientChannel() { + _i1.throwOnMissingStub(this); + } + + @override + Object get host => (super.noSuchMethod( + Invocation.getter(#host), + returnValue: _FakeObject_0( + this, + Invocation.getter(#host), + ), + ) as Object); + + @override + int get port => (super.noSuchMethod( + Invocation.getter(#port), + returnValue: 0, + ) as int); + + @override + _i2.ChannelOptions get options => (super.noSuchMethod( + Invocation.getter(#options), + returnValue: _FakeChannelOptions_1( + this, + Invocation.getter(#options), + ), + ) as _i2.ChannelOptions); + + @override + _i9.Stream<_i3.ConnectionState> get onConnectionStateChanged => + (super.noSuchMethod( + Invocation.getter(#onConnectionStateChanged), + returnValue: _i9.Stream<_i3.ConnectionState>.empty(), + ) as _i9.Stream<_i3.ConnectionState>); + + @override + _i3.ClientConnection createConnection() => (super.noSuchMethod( + Invocation.method( + #createConnection, + [], + ), + returnValue: _FakeClientConnection_2( + this, + Invocation.method( + #createConnection, + [], + ), + ), + ) as _i3.ClientConnection); + + @override + _i9.Future shutdown() => (super.noSuchMethod( + Invocation.method( + #shutdown, + [], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future terminate() => (super.noSuchMethod( + Invocation.method( + #terminate, + [], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future<_i3.ClientConnection> getConnection() => (super.noSuchMethod( + Invocation.method( + #getConnection, + [], + ), + returnValue: + _i9.Future<_i3.ClientConnection>.value(_FakeClientConnection_2( + this, + Invocation.method( + #getConnection, + [], + ), + )), + ) as _i9.Future<_i3.ClientConnection>); + + @override + _i4.ClientCall createCall( + _i11.ClientMethod? method, + _i9.Stream? requests, + _i4.CallOptions? options, + ) => + (super.noSuchMethod( + Invocation.method( + #createCall, + [ + method, + requests, + options, + ], + ), + returnValue: _FakeClientCall_3( + this, + Invocation.method( + #createCall, + [ + method, + requests, + options, + ], + ), + ), + ) as _i4.ClientCall); +} + +/// A class which mocks [FirebaseAppCheck]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockFirebaseAppCheck extends _i1.Mock implements _i12.FirebaseAppCheck { + MockFirebaseAppCheck() { + _i1.throwOnMissingStub(this); + } + + @override + _i5.FirebaseApp get app => (super.noSuchMethod( + Invocation.getter(#app), + returnValue: _FakeFirebaseApp_4( + this, + Invocation.getter(#app), + ), + ) as _i5.FirebaseApp); + + @override + set app(_i5.FirebaseApp? _app) => super.noSuchMethod( + Invocation.setter( + #app, + _app, + ), + returnValueForMissingStub: null, + ); + + @override + _i9.Stream get onTokenChange => (super.noSuchMethod( + Invocation.getter(#onTokenChange), + returnValue: _i9.Stream.empty(), + ) as _i9.Stream); + + @override + Map get pluginConstants => (super.noSuchMethod( + Invocation.getter(#pluginConstants), + returnValue: {}, + ) as Map); + + @override + _i9.Future activate({ + _i13.WebProvider? webProvider, + _i13.AndroidProvider? androidProvider = _i13.AndroidProvider.playIntegrity, + _i13.AppleProvider? appleProvider = _i13.AppleProvider.deviceCheck, + }) => + (super.noSuchMethod( + Invocation.method( + #activate, + [], + { + #webProvider: webProvider, + #androidProvider: androidProvider, + #appleProvider: appleProvider, + }, + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future getToken([bool? forceRefresh]) => (super.noSuchMethod( + Invocation.method( + #getToken, + [forceRefresh], + ), + returnValue: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future setTokenAutoRefreshEnabled( + bool? isTokenAutoRefreshEnabled) => + (super.noSuchMethod( + Invocation.method( + #setTokenAutoRefreshEnabled, + [isTokenAutoRefreshEnabled], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future getLimitedUseToken() => (super.noSuchMethod( + Invocation.method( + #getLimitedUseToken, + [], + ), + returnValue: _i9.Future.value(_i14.dummyValue( + this, + Invocation.method( + #getLimitedUseToken, + [], + ), + )), + ) as _i9.Future); +} + +/// A class which mocks [User]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockUser extends _i1.Mock implements _i7.User { + MockUser() { + _i1.throwOnMissingStub(this); + } + + @override + bool get emailVerified => (super.noSuchMethod( + Invocation.getter(#emailVerified), + returnValue: false, + ) as bool); + + @override + bool get isAnonymous => (super.noSuchMethod( + Invocation.getter(#isAnonymous), + returnValue: false, + ) as bool); + + @override + _i6.UserMetadata get metadata => (super.noSuchMethod( + Invocation.getter(#metadata), + returnValue: _FakeUserMetadata_5( + this, + Invocation.getter(#metadata), + ), + ) as _i6.UserMetadata); + + @override + List<_i6.UserInfo> get providerData => (super.noSuchMethod( + Invocation.getter(#providerData), + returnValue: <_i6.UserInfo>[], + ) as List<_i6.UserInfo>); + + @override + String get uid => (super.noSuchMethod( + Invocation.getter(#uid), + returnValue: _i14.dummyValue( + this, + Invocation.getter(#uid), + ), + ) as String); + + @override + _i7.MultiFactor get multiFactor => (super.noSuchMethod( + Invocation.getter(#multiFactor), + returnValue: _FakeMultiFactor_6( + this, + Invocation.getter(#multiFactor), + ), + ) as _i7.MultiFactor); + + @override + _i9.Future delete() => (super.noSuchMethod( + Invocation.method( + #delete, + [], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future getIdToken([bool? forceRefresh = false]) => + (super.noSuchMethod( + Invocation.method( + #getIdToken, + [forceRefresh], + ), + returnValue: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future<_i6.IdTokenResult> getIdTokenResult( + [bool? forceRefresh = false]) => + (super.noSuchMethod( + Invocation.method( + #getIdTokenResult, + [forceRefresh], + ), + returnValue: _i9.Future<_i6.IdTokenResult>.value(_FakeIdTokenResult_7( + this, + Invocation.method( + #getIdTokenResult, + [forceRefresh], + ), + )), + ) as _i9.Future<_i6.IdTokenResult>); + + @override + _i9.Future<_i7.UserCredential> linkWithCredential( + _i6.AuthCredential? credential) => + (super.noSuchMethod( + Invocation.method( + #linkWithCredential, + [credential], + ), + returnValue: _i9.Future<_i7.UserCredential>.value(_FakeUserCredential_8( + this, + Invocation.method( + #linkWithCredential, + [credential], + ), + )), + ) as _i9.Future<_i7.UserCredential>); + + @override + _i9.Future<_i7.UserCredential> linkWithProvider(_i6.AuthProvider? provider) => + (super.noSuchMethod( + Invocation.method( + #linkWithProvider, + [provider], + ), + returnValue: _i9.Future<_i7.UserCredential>.value(_FakeUserCredential_8( + this, + Invocation.method( + #linkWithProvider, + [provider], + ), + )), + ) as _i9.Future<_i7.UserCredential>); + + @override + _i9.Future<_i7.UserCredential> reauthenticateWithProvider( + _i6.AuthProvider? provider) => + (super.noSuchMethod( + Invocation.method( + #reauthenticateWithProvider, + [provider], + ), + returnValue: _i9.Future<_i7.UserCredential>.value(_FakeUserCredential_8( + this, + Invocation.method( + #reauthenticateWithProvider, + [provider], + ), + )), + ) as _i9.Future<_i7.UserCredential>); + + @override + _i9.Future<_i7.UserCredential> reauthenticateWithPopup( + _i6.AuthProvider? provider) => + (super.noSuchMethod( + Invocation.method( + #reauthenticateWithPopup, + [provider], + ), + returnValue: _i9.Future<_i7.UserCredential>.value(_FakeUserCredential_8( + this, + Invocation.method( + #reauthenticateWithPopup, + [provider], + ), + )), + ) as _i9.Future<_i7.UserCredential>); + + @override + _i9.Future reauthenticateWithRedirect(_i6.AuthProvider? provider) => + (super.noSuchMethod( + Invocation.method( + #reauthenticateWithRedirect, + [provider], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future<_i7.UserCredential> linkWithPopup(_i6.AuthProvider? provider) => + (super.noSuchMethod( + Invocation.method( + #linkWithPopup, + [provider], + ), + returnValue: _i9.Future<_i7.UserCredential>.value(_FakeUserCredential_8( + this, + Invocation.method( + #linkWithPopup, + [provider], + ), + )), + ) as _i9.Future<_i7.UserCredential>); + + @override + _i9.Future linkWithRedirect(_i6.AuthProvider? provider) => + (super.noSuchMethod( + Invocation.method( + #linkWithRedirect, + [provider], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future<_i7.ConfirmationResult> linkWithPhoneNumber( + String? phoneNumber, [ + _i7.RecaptchaVerifier? verifier, + ]) => + (super.noSuchMethod( + Invocation.method( + #linkWithPhoneNumber, + [ + phoneNumber, + verifier, + ], + ), + returnValue: + _i9.Future<_i7.ConfirmationResult>.value(_FakeConfirmationResult_9( + this, + Invocation.method( + #linkWithPhoneNumber, + [ + phoneNumber, + verifier, + ], + ), + )), + ) as _i9.Future<_i7.ConfirmationResult>); + + @override + _i9.Future<_i7.UserCredential> reauthenticateWithCredential( + _i6.AuthCredential? credential) => + (super.noSuchMethod( + Invocation.method( + #reauthenticateWithCredential, + [credential], + ), + returnValue: _i9.Future<_i7.UserCredential>.value(_FakeUserCredential_8( + this, + Invocation.method( + #reauthenticateWithCredential, + [credential], + ), + )), + ) as _i9.Future<_i7.UserCredential>); + + @override + _i9.Future reload() => (super.noSuchMethod( + Invocation.method( + #reload, + [], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future sendEmailVerification( + [_i6.ActionCodeSettings? actionCodeSettings]) => + (super.noSuchMethod( + Invocation.method( + #sendEmailVerification, + [actionCodeSettings], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future<_i7.User> unlink(String? providerId) => (super.noSuchMethod( + Invocation.method( + #unlink, + [providerId], + ), + returnValue: _i9.Future<_i7.User>.value(_FakeUser_10( + this, + Invocation.method( + #unlink, + [providerId], + ), + )), + ) as _i9.Future<_i7.User>); + + @override + _i9.Future updateEmail(String? newEmail) => (super.noSuchMethod( + Invocation.method( + #updateEmail, + [newEmail], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future updatePassword(String? newPassword) => (super.noSuchMethod( + Invocation.method( + #updatePassword, + [newPassword], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future updatePhoneNumber( + _i6.PhoneAuthCredential? phoneCredential) => + (super.noSuchMethod( + Invocation.method( + #updatePhoneNumber, + [phoneCredential], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future updateDisplayName(String? displayName) => + (super.noSuchMethod( + Invocation.method( + #updateDisplayName, + [displayName], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future updatePhotoURL(String? photoURL) => (super.noSuchMethod( + Invocation.method( + #updatePhotoURL, + [photoURL], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future updateProfile({ + String? displayName, + String? photoURL, + }) => + (super.noSuchMethod( + Invocation.method( + #updateProfile, + [], + { + #displayName: displayName, + #photoURL: photoURL, + }, + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future verifyBeforeUpdateEmail( + String? newEmail, [ + _i6.ActionCodeSettings? actionCodeSettings, + ]) => + (super.noSuchMethod( + Invocation.method( + #verifyBeforeUpdateEmail, + [ + newEmail, + actionCodeSettings, + ], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); +} + +/// A class which mocks [ConnectorServiceClient]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockConnectorServiceClient extends _i1.Mock + implements _i15.ConnectorServiceClient { + MockConnectorServiceClient() { + _i1.throwOnMissingStub(this); + } + + @override + _i8.ResponseFuture<_i16.ExecuteQueryResponse> executeQuery( + _i16.ExecuteQueryRequest? request, { + _i4.CallOptions? options, + }) => + (super.noSuchMethod( + Invocation.method( + #executeQuery, + [request], + {#options: options}, + ), + returnValue: _FakeResponseFuture_11<_i16.ExecuteQueryResponse>( + this, + Invocation.method( + #executeQuery, + [request], + {#options: options}, + ), + ), + ) as _i8.ResponseFuture<_i16.ExecuteQueryResponse>); + + @override + _i8.ResponseFuture<_i16.ExecuteMutationResponse> executeMutation( + _i16.ExecuteMutationRequest? request, { + _i4.CallOptions? options, + }) => + (super.noSuchMethod( + Invocation.method( + #executeMutation, + [request], + {#options: options}, + ), + returnValue: _FakeResponseFuture_11<_i16.ExecuteMutationResponse>( + this, + Invocation.method( + #executeMutation, + [request], + {#options: options}, + ), + ), + ) as _i8.ResponseFuture<_i16.ExecuteMutationResponse>); + + @override + _i4.ClientCall $createCall( + _i11.ClientMethod? method, + _i9.Stream? requests, { + _i4.CallOptions? options, + }) => + (super.noSuchMethod( + Invocation.method( + #$createCall, + [ + method, + requests, + ], + {#options: options}, + ), + returnValue: _FakeClientCall_3( + this, + Invocation.method( + #$createCall, + [ + method, + requests, + ], + {#options: options}, + ), + ), + ) as _i4.ClientCall); + + @override + _i8.ResponseFuture $createUnaryCall( + _i11.ClientMethod? method, + Q? request, { + _i4.CallOptions? options, + }) => + (super.noSuchMethod( + Invocation.method( + #$createUnaryCall, + [ + method, + request, + ], + {#options: options}, + ), + returnValue: _FakeResponseFuture_11( + this, + Invocation.method( + #$createUnaryCall, + [ + method, + request, + ], + {#options: options}, + ), + ), + ) as _i8.ResponseFuture); + + @override + _i8.ResponseStream $createStreamingCall( + _i11.ClientMethod? method, + _i9.Stream? requests, { + _i4.CallOptions? options, + }) => + (super.noSuchMethod( + Invocation.method( + #$createStreamingCall, + [ + method, + requests, + ], + {#options: options}, + ), + returnValue: _FakeResponseStream_12( + this, + Invocation.method( + #$createStreamingCall, + [ + method, + requests, + ], + {#options: options}, + ), + ), + ) as _i8.ResponseStream); +} + +/// A class which mocks [ResponseFuture]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockResponseFuture extends _i1.Mock implements _i8.ResponseFuture { + MockResponseFuture() { + _i1.throwOnMissingStub(this); + } + + @override + _i9.Future> get headers => (super.noSuchMethod( + Invocation.getter(#headers), + returnValue: _i9.Future>.value({}), + ) as _i9.Future>); + + @override + _i9.Future> get trailers => (super.noSuchMethod( + Invocation.getter(#trailers), + returnValue: _i9.Future>.value({}), + ) as _i9.Future>); + + @override + _i9.Stream asStream() => (super.noSuchMethod( + Invocation.method( + #asStream, + [], + ), + returnValue: _i9.Stream.empty(), + ) as _i9.Stream); + + @override + _i9.Future catchError( + Function? onError, { + bool Function(Object)? test, + }) => + (super.noSuchMethod( + Invocation.method( + #catchError, + [onError], + {#test: test}, + ), + returnValue: _i14.ifNotNull( + _i14.dummyValueOrNull( + this, + Invocation.method( + #catchError, + [onError], + {#test: test}, + ), + ), + (R v) => _i9.Future.value(v), + ) ?? + _FakeFuture_13( + this, + Invocation.method( + #catchError, + [onError], + {#test: test}, + ), + ), + ) as _i9.Future); + + @override + _i9.Future then( + _i9.FutureOr Function(R)? onValue, { + Function? onError, + }) => + (super.noSuchMethod( + Invocation.method( + #then, + [onValue], + {#onError: onError}, + ), + returnValue: _i14.ifNotNull( + _i14.dummyValueOrNull( + this, + Invocation.method( + #then, + [onValue], + {#onError: onError}, + ), + ), + (S v) => _i9.Future.value(v), + ) ?? + _FakeFuture_13( + this, + Invocation.method( + #then, + [onValue], + {#onError: onError}, + ), + ), + ) as _i9.Future); + + @override + _i9.Future whenComplete(_i9.FutureOr Function()? action) => + (super.noSuchMethod( + Invocation.method( + #whenComplete, + [action], + ), + returnValue: _i14.ifNotNull( + _i14.dummyValueOrNull( + this, + Invocation.method( + #whenComplete, + [action], + ), + ), + (R v) => _i9.Future.value(v), + ) ?? + _FakeFuture_13( + this, + Invocation.method( + #whenComplete, + [action], + ), + ), + ) as _i9.Future); + + @override + _i9.Future timeout( + Duration? timeLimit, { + _i9.FutureOr Function()? onTimeout, + }) => + (super.noSuchMethod( + Invocation.method( + #timeout, + [timeLimit], + {#onTimeout: onTimeout}, + ), + returnValue: _i14.ifNotNull( + _i14.dummyValueOrNull( + this, + Invocation.method( + #timeout, + [timeLimit], + {#onTimeout: onTimeout}, + ), + ), + (R v) => _i9.Future.value(v), + ) ?? + _FakeFuture_13( + this, + Invocation.method( + #timeout, + [timeLimit], + {#onTimeout: onTimeout}, + ), + ), + ) as _i9.Future); + + @override + _i9.Future cancel() => (super.noSuchMethod( + Invocation.method( + #cancel, + [], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); +} diff --git a/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.dart b/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.dart index af891aa12f07..780d42884f04 100644 --- a/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.dart +++ b/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.dart @@ -183,8 +183,7 @@ void main() { )).called(1); }); - test('invokeOperation should include auth and appCheck tokens in headers', - () async { + test('invokeOperation should include auth tokens in headers', () async { final mockResponse = http.Response('{"data": {"key": "value"}}', 200); when(mockHttpClient.post(any, headers: anyNamed('headers'), body: anyNamed('body'))) @@ -208,6 +207,30 @@ void main() { )).called(1); }); + test('invokeOperation should include appcheck tokens in headers', () async { + final mockResponse = http.Response('{"data": {"key": "value"}}', 200); + when(mockHttpClient.post(any, + headers: anyNamed('headers'), body: anyNamed('body'))) + .thenAnswer((_) async => mockResponse); + + when(mockUser.getIdToken()).thenAnswer((_) async => 'authToken123'); + when(mockAppCheck.getToken()).thenAnswer((_) async => 'appCheckToken123'); + + final deserializer = (String data) => 'Deserialized Data'; + + await transport.invokeOperation('testQuery', 'testEndpoint', deserializer, + null, null, 'executeQuery'); + + verify(mockHttpClient.post( + any, + headers: argThat( + containsPair('X-Firebase-AppCheck', 'appCheckToken123'), + named: 'headers', + ), + body: anyNamed('body'), + )).called(1); + }); + test( 'invokeOperation should handle missing auth and appCheck tokens gracefully', () async { @@ -233,6 +256,7 @@ void main() { body: anyNamed('body'), )).called(1); }); + test('invokeOperation should throw an error if the server throws one', () async { final mockResponse = http.Response(""" diff --git a/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.mocks.dart b/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.mocks.dart index cb633caecf6f..244aad50e971 100644 --- a/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.mocks.dart +++ b/packages/firebase_data_connect/firebase_data_connect/test/src/network/rest_transport_test.mocks.dart @@ -1,17 +1,3 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - // Mocks generated by Mockito 5.4.4 from annotations // in firebase_data_connect/test/src/network/rest_transport_test.dart. // Do not manually edit this file.