Firebase Data Connect를 사용하면 Google Cloud SQL로 관리되는 PostgreSQL 인스턴스의 커넥터를 만들 수 있습니다. 이러한 커넥터는 데이터를 사용하는 스키마, 쿼리, 변형의 조합입니다.
시작 가이드에서는 PostgreSQL용 영화 리뷰 앱 스키마를 소개했으며, 이 가이드에서는 PostgreSQL용 Data Connect 스키마를 설계하는 방법을 자세히 살펴봅니다.
이 가이드에서는 Data Connect 쿼리와 변형을 스키마 예시와 함께 설명합니다. Data Connect 스키마에 관한 가이드에서 쿼리 (및 변형)를 논의하는 이유는 무엇인가요? 다른 GraphQL 기반 플랫폼과 마찬가지로 Firebase Data Connect는 쿼리 우선 개발 플랫폼이므로 개발자는 데이터 모델링 시 클라이언트에게 필요한 데이터를 고려하게 되며 이는 프로젝트용으로 개발하는 데이터 스키마에 큰 영향을 미칩니다.
이 가이드는 새로운 영화 리뷰 스키마로 시작하여 이 스키마에서 파생된 쿼리 및 변형을 다루고 마지막으로 핵심 Data Connect 스키마와 동일한 SQL 목록을 제공합니다.
영화 리뷰 앱의 스키마
사용자가 영화 리뷰를 제출하고 볼 수 있는 서비스를 빌드하려고 한다고 가정해 보겠습니다.
이러한 앱에는 초기 스키마가 필요합니다. 나중에 이 스키마를 확장하여 복잡한 관계형 쿼리를 만들 것입니다.
영화 표
영화 스키마에는 다음과 같은 핵심 지시어가 포함됩니다.
@table(name)
및@col(name)
: SQL 테이블 및 열 이름을 맞춤설정합니다. 지정하지 않으면 Data Connect에서 snake_case 이름을 생성합니다.@col(dataType)
를 사용하여 SQL 열 유형을 맞춤설정합니다.@default
: 삽입 중에 SQL 열 기본값을 구성합니다.
자세한 내용은 @table
, @col
, @default
의 참조 문서를 확인하세요.
# Movies
type Movie @table(name: "movie", key: "id") {
id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
title: String!
releaseYear: Int
genre: String @col(dataType: "varchar(20)")
rating: Int
description: String
}
키 스칼라 및 서버 값
영화 리뷰 앱을 자세히 살펴보기 전에 Data Connect 키 스칼라 및 서버 값을 소개하겠습니다.
키 스칼라는 Data Connect가 스키마의 키 필드에서 자동으로 조합하는 간결한 객체 식별자입니다. 키 스칼라의 목적은 효율성입니다. 단일 호출에서 데이터의 ID 및 구조에 관한 정보를 찾을 수 있습니다. 이는 새 레코드에 순차 작업을 실행하고 향후 작업에 전달할 고유 식별자가 필요하거나 관계형 키에 액세스하여 더 복잡한 작업을 추가로 실행하려는 경우에 특히 유용합니다.
서버 값을 사용하면 서버가 expr
인수의 특정 서버 측 CEL 표현식에 따라 저장된 값 또는 쉽게 계산할 수 있는 값을 사용하여 테이블의 필드를 동적으로 채울 수 있습니다. 예를 들어 작업 요청 updatedAt: Timestamp! @default(expr: "request.time")
에 저장된 시간을 사용하여 필드에 액세스할 때 적용되는 타임스탬프가 있는 필드를 정의할 수 있습니다.
영화 메타데이터 테이블
이제 영화 감독을 추적하고 Movie
와 일대일 관계를 설정해 보겠습니다.
참조 필드를 추가하여 관계를 정의합니다.
@ref
디렉티브를 사용하여 외래 키 제약조건을 맞춤설정할 수 있습니다.
@ref(fields)
: 외래 키 필드를 지정합니다.@ref(references)
를 사용하여 대상 테이블에서 참조되는 필드를 지정합니다. 이 참조는 기본 키로 기본 설정되지만@unique
가 있는 필드도 지원됩니다.
자세한 내용은 @ref
의 참조 문서를 확인하세요.
# Movie Metadata
# Movie - MovieMetadata is a one-to-one relationship
type MovieMetadata @table {
# @unique ensures that each Movie only has one MovieMetadata.
movie: Movie! @unique
# Since it references to another table type, it adds a foreign key constraint.
# movie: Movie! @unique @ref(fields: "movieId", references: "id")
# movieId: UUID! <- implicitly added foreign key field
director: String
}
Actor 및 MovieActor
다음으로 영화에 출연하는 배우를 추가하려고 합니다. 영화와 배우 간에 다대다 관계가 있으므로 조인 테이블을 만듭니다.
# Actors
# Suppose an actor can participate in multiple movies and movies can have multiple actors
# Movie - Actors (or vice versa) is a many to many relationship
type Actor @table {
id: UUID! @default(expr: "uuidV4()")
name: String! @col(dataType: "varchar(30)")
}
# Join table for many-to-many relationship for movies and actors
# The 'key' param signifies the primary keys of this table
# In this case, the keys are [movieId, actorId], the foreign key fields of the reference fields [movie, actor]
type MovieActor @table(key: ["movie", "actor"]) {
movie: Movie!
# movieId: UUID! <- implicitly added foreign key field
actor: Actor!
# actorId: UUID! <- implicitly added foreign key field
role: String! # "main" or "supporting"
# optional other fields
}
사용자
마지막으로 앱 사용자입니다.
# Users
# Suppose a user can leave reviews for movies
type User @table {
id: String! @default(expr: "auth.uid")
username: String! @col(dataType: "varchar(50)")
}
지원되는 데이터 유형
Data Connect는 @col(dataType:)
를 사용하여 PostgreSQL 유형에 할당하는 다음 스칼라 데이터 유형을 지원합니다.
Data Connect 유형 | GraphQL 기본 제공 유형 또는 Data Connect 맞춤 유형 |
기본 PostgreSQL 유형 | 지원되는 PostgreSQL 유형 (괄호 안의 별칭) |
---|---|---|---|
문자열 | GraphQL | 텍스트 | text bit(n), varbit(n) char(n), varchar(n) |
정수 | GraphQL | int | Int2 (smallint, smallserial), int4 (integer, int, serial) |
부동 소수점 수 | GraphQL | float8 | float4 (실수) float8 (배정밀도) 숫자 (소수점) |
불리언 | GraphQL | 부울 | 부울 |
UUID | 커스텀 | uuid | uuid |
Int64 | 커스텀 | bigint | int8 (bigint, bigserial) 숫자 (소수점) |
날짜 | 커스텀 | date | 날짜 |
타임스탬프 | 커스텀 | timestamptz | timestamptz 참고: 로컬 시간대 정보는 저장되지 않습니다. |
벡터 | 커스텀 | vector | 벡터 Vertex AI로 벡터 유사성 검색 수행을 참고하세요. |
- GraphQL
List
는 1차원 배열에 매핑됩니다.- 예를 들어
[Int]
는int5[]
에 매핑되고[Any]
는jsonb[]
에 매핑됩니다. - Data Connect는 중첩 배열을 지원하지 않습니다.
- 예를 들어
생성된 필드를 사용하여 쿼리 및 변형 빌드
Data Connect 쿼리 및 변형은 스키마의 유형 및 유형 관계를 기반으로 자동으로 생성된 Data Connect 필드 집합을 확장합니다. 이 필드는 스키마를 수정할 때마다 로컬 도구에서 생성됩니다.
시작 가이드에서 확인한 바와 같이 Firebase 콘솔과 로컬 개발 도구는 이러한 자동 생성된 필드를 사용하여 데이터를 시드하고 테이블의 콘텐츠를 확인하는 데 사용할 수 있는 임시 관리 쿼리 및 변형을 제공합니다.
개발 프로세스에서는 이러한 자동 생성된 필드를 기반으로 커넥터에 번들로 묶인 배포 가능한 쿼리 및 배포 가능한 변형을 구현합니다.
자동 생성된 필드 이름 지정
Data Connect는 스키마 유형 선언을 기반으로 자동 생성된 필드에 적합한 이름을 추론합니다. 예를 들어 PostgreSQL 소스를 사용하는 경우 Movie
라는 테이블을 정의하면 서버에서 다음을 생성합니다.
- 단일 테이블 사용 사례에서 데이터를 읽는 필드로, 친숙한 이름은
movie
(단수,eq
와 같은 인수를 전달하여 개별 결과를 검색하는 경우) 및movies
(복수,gt
와 같은 인수 및orderby
와 같은 작업을 전달하여 결과 목록을 검색하는 경우)입니다. Data Connect는actors_on_movies
또는actors_via_actormovie
와 같은 명시적인 이름을 사용하여 다중 테이블 관계형 작업을 위한 필드도 생성합니다. movie_insert
,movie_upsert
와 같은 익숙한 이름으로 데이터를 쓰는 필드
또한 스키마 정의 언어를 사용하면 singular
및 plural
지시어 인수를 사용하여 필드의 이름이 생성되는 방식을 명시적으로 제어할 수 있습니다.
쿼리 및 변형을 위한 디렉티브
유형 및 테이블을 정의하는 데 사용하는 디렉터리 외에도 Data Connect는 쿼리 및 변형의 동작을 보강하기 위한 @auth
, @check
, @redact
, @transaction
디렉터리를 제공합니다.
지시문 | 적용 대상 | 설명 |
---|---|---|
@auth |
쿼리 및 변형 | 쿼리 또는 변형의 인증 정책을 정의합니다. 승인 및 증명 가이드를 참고하세요. |
@check |
승인 데이터 조회 쿼리 | 지정된 필드가 쿼리 결과에 있는지 확인합니다. Common Expression Language (CEL) 표현식은 필드 값을 테스트하는 데 사용됩니다. 승인 및 증명 가이드를 참고하세요. |
@redact |
쿼리 | 클라이언트의 응답 일부를 삭제합니다. 승인 및 증명 가이드를 참고하세요. |
@transaction |
변형 | 변형이 항상 데이터베이스 트랜잭션에서 실행되도록 적용합니다. 영화 앱 변형 예를 참고하세요. |
영화 리뷰 데이터베이스에 대한 쿼리
쿼리 작업 유형 선언, 작업 이름, 0개 이상의 작업 인수, 인수가 있는 0개 이상의 디렉터리로 Data Connect 쿼리를 정의합니다.
빠른 시작에서 listEmails
쿼리 예시에는 매개변수가 사용되지 않았습니다. 물론 대부분의 경우 쿼리 필드에 전달되는 데이터는 동적입니다. $variableName
문법을 사용하여 변수를 쿼리 정의의 구성요소 중 하나로 사용할 수 있습니다.
따라서 다음 쿼리에는 다음이 포함됩니다.
query
유형 정의ListMoviesByGenre
작업 (쿼리) 이름- 단일 변수
$genre
작업 인수 - 단일 지시문
@auth
query ListMoviesByGenre($genre: String!) @auth(level: USER)
모든 쿼리 인수에는 유형 선언, String
와 같은 내장 유형 또는 Movie
와 같은 맞춤 스키마 정의 유형이 필요합니다.
점점 더 복잡해지는 쿼리의 서명을 살펴보겠습니다. 마지막으로 배포 가능한 쿼리를 빌드하는 데 사용할 수 있는 강력하고 간결한 관계 표현식을 소개합니다.
쿼리의 주요 스칼라
먼저 키 스칼라에 관한 참고사항을 알아보겠습니다.
Data Connect는 _Key
로 식별되는 키 스칼라의 특수 유형을 정의합니다. 예를 들어 Movie
테이블의 키 스칼라 유형은 Movie_Key
입니다.
키 스칼라를 대부분의 자동 생성 읽기 필드에서 반환된 응답으로 검색하거나 스칼라 키를 빌드하는 데 필요한 모든 필드를 검색한 쿼리에서 검색합니다.
실행 예시의 movie
와 같은 단일 자동 쿼리는 키 스칼라를 허용하는 키 인수를 지원합니다.
키 스칼라를 리터럴로 전달할 수 있습니다. 하지만 키 스칼라를 입력으로 전달하도록 변수를 정의할 수 있습니다.
query GetMovie($myKey: Movie_Key!) {
movie(key: $myKey) { title }
}
다음과 같이 요청 JSON (또는 다른 직렬화 형식)에 제공할 수 있습니다.
{
# …
"variables": {
"myKey": {"foo": "some-string-value", "bar": 42}
}
}
맞춤 스칼라 파싱 덕분에 Movie_Key
는 변수가 포함될 수 있는 객체 문법을 사용하여 구성할 수도 있습니다. 이는 어떤 이유로든 개별 구성요소를 여러 변수로 분할하려는 경우에 주로 유용합니다.
쿼리의 별칭
Data Connect는 쿼리에서 GraphQL 별칭을 지원합니다. 별칭을 사용하면 쿼리 결과에 반환되는 데이터의 이름을 바꿀 수 있습니다. 단일 Data Connect 쿼리는 서버에 대한 하나의 효율적인 요청에서 여러 필터 또는 기타 쿼리 작업을 적용하여 여러 '하위 쿼리'를 한 번에 실행할 수 있습니다. 반환된 데이터 세트에서 이름 충돌을 방지하려면 별칭을 사용하여 하위 쿼리를 구분합니다.
다음은 표현식에서 별칭 mostPopular
를 사용하는 쿼리입니다.
query ReviewTopPopularity($genre: String) {
mostPopular: review(first: {
where: {genre: {eq: $genre}},
orderBy: {popularity: DESC}
}) { … }
}
필터가 있는 간단한 쿼리
Data Connect 쿼리는 모든 일반적인 SQL 필터 및 순서 연산에 매핑됩니다.
where
및 orderBy
연산자 (단수, 복수 쿼리)
테이블에서 일치하는 모든 행 (및 중첩된 연결)을 반환합니다. 필터와 일치하는 레코드가 없으면 빈 배열을 반환합니다.
query MovieByTopRating($genre: String) {
mostPopular: movies(
where: { genre: { eq: $genre } }, orderBy: { rating: DESC }
) {
# graphql: list the fields from the results to return
id
title
genre
description
}
}
query MoviesByReleaseYear($min: Int, $max: Int) {
movies(where: {releaseYear: {le: $max, ge: $min}}, orderBy: [{releaseYear: ASC}]) { … }
}
limit
및 offset
연산자 (단수, 복수 쿼리)
결과에 페이지로 나누기를 실행할 수 있습니다. 이러한 인수는 허용되지만 결과로 반환되지 않습니다.
query MoviesTop10 {
movies(orderBy: [{ rating: DESC }], limit: 10) {
# graphql: list the fields from the results to return
title
}
}
배열 필드의 포함
배열 필드에 지정된 항목이 포함되는지 테스트할 수 있습니다.
# Filter using arrays and embedded fields.
query ListMoviesByTag($tag: String!) {
movies(where: { tags: { includes: $tag }}) {
# graphql: list the fields from the results to return
id
title
}
}
문자열 연산 및 정규 표현식
쿼리에서 정규 표현식을 비롯한 일반적인 문자열 검색 및 비교 작업을 사용할 수 있습니다. 효율성을 위해 여기에서 여러 작업을 번들로 묶고 별칭으로 구분합니다.
query MoviesTitleSearch($prefix: String, $suffix: String, $contained: String, $regex: String) {
prefixed: movies(where: {title: {startsWith: $prefix}}) {...}
suffixed: movies(where: {title: {endsWith: $suffix}}) {...}
contained: movies(where: {title: {contains: $contained}}) {...}
matchRegex: movies(where: {title: {pattern: {regex: $regex}}}) {...}
}
구성된 필터의 or
및 and
더 복잡한 로직에는 or
및 and
를 사용합니다.
query ListMoviesByGenreAndGenre($minRating: Int!, $genre: String) {
movies(
where: { _or: [{ rating: { ge: $minRating } }, { genre: { eq: $genre } }] }
) {
# graphql: list the fields from the results to return
title
}
}
복합 쿼리
Data Connect 쿼리는 테이블 간의 관계를 기반으로 데이터에 액세스할 수 있습니다. 스키마에 정의된 객체 (일대일) 또는 배열 (일대다) 관계를 사용하여 중첩 쿼리를 실행할 수 있습니다. 즉, 중첩된 유형 또는 관련 유형의 데이터와 함께 한 유형의 데이터를 가져올 수 있습니다.
이러한 쿼리는 생성된 읽기 필드에서 매직 Data Connect _on_
및 _via
구문을 사용합니다.
초기 버전의 스키마를 수정합니다.
다대일
Review
테이블과 User
수정을 통해 앱에 리뷰를 추가해 보겠습니다.
# User table is keyed by Firebase Auth UID.
type User @table {
# `@default(expr: "auth.uid")` sets it to Firebase Auth UID during insert and upsert.
id: String! @default(expr: "auth.uid")
username: String! @col(dataType: "varchar(50)")
# The `user: User!` field in the Review table generates the following one-to-many query field.
# reviews_on_user: [Review!]!
# The `Review` join table the following many-to-many query field.
# movies_via_Review: [Movie!]!
}
# Reviews is a join table tween User and Movie.
# It has a composite primary keys `userUid` and `movieId`.
# A user can leave reviews for many movies. A movie can have reviews from many users.
# User <-> Review is a one-to-many relationship
# Movie <-> Review is a one-to-many relationship
# Movie <-> User is a many-to-many relationship
type Review @table(name: "Reviews", key: ["movie", "user"]) {
user: User!
# The user field adds the following foreign key field. Feel free to uncomment and customize it.
# userUid: String!
movie: Movie!
# The movie field adds the following foreign key field. Feel free to uncomment and customize it.
# movieId: UUID!
rating: Int
reviewText: String
reviewDate: Date! @default(expr: "request.time")
}
다대일 쿼리
이제 별칭이 있는 쿼리를 살펴보고 _via_
문법을 설명해 보겠습니다.
query UserMoviePreferences($username: String!) @auth(level: USER) {
users(where: { username: { eq: $username } }) {
likedMovies: movies_via_Review(where: { rating: { ge: 4 } }) {
title
genre
}
dislikedMovies: movies_via_Review(where: { rating: { le: 2 } }) {
title
genre
}
}
}
일대일
패턴을 확인할 수 있습니다. 아래에서는 설명을 위해 스키마를 수정했습니다.
# Movies
type Movie
@table(name: "Movies", singular: "movie", plural: "movies", key: ["id"]) {
id: UUID! @col(name: "movie_id") @default(expr: "uuidV4()")
title: String!
releaseYear: Int @col(name: "release_year")
genre: String
rating: Int @col(name: "rating")
description: String @col(name: "description")
tags: [String] @col(name: "tags")
}
# Movie Metadata
# Movie - MovieMetadata is a one-to-one relationship
type MovieMetadata
@table(
name: "MovieMetadata"
) {
# @ref creates a field in the current table (MovieMetadata) that holds the primary key of the referenced type
# In this case, @ref(fields: "id") is implied
movie: Movie! @ref
# movieId: UUID <- this is created by the above @ref
director: String @col(name: "director")
}
extend type MovieMetadata {
movieId: UUID! # matches primary key of referenced type
...
}
extend type Movie {
movieMetadata: MovieMetadata # can only be non-nullable on ref side
# conflict-free name, always generated
movieMetadatas_on_movie: MovieMetadata
}
일대일 쿼리
_on_
문법을 사용하여 쿼리할 수 있습니다.
# One to one
query GetMovieMetadata($id: UUID!) @auth(level: PUBLIC) {
movie(id: $id) {
movieMetadatas_on_movie {
director
}
}
}
다대다
영화에는 배우가 필요하고 배우에게는 영화가 필요합니다. MovieActors
조인 테이블로 모델링할 수 있는 다대다 관계가 있습니다.
# MovieActors Join Table Definition
type MovieActors @table(
key: ["movie", "actor"] # join key triggers many-to-many generation
) {
movie: Movie!
actor: Actor!
}
# generated extensions for the MovieActors join table
extend type MovieActors {
movieId: UUID!
actorId: UUID!
}
# Extensions for Actor and Movie to handle many-to-many relationships
extend type Movie {
movieActors: [MovieActors!]! # standard many-to-one relation to join table
actors: [Actor!]! # many-to-many via join table
movieActors_on_actor: [MovieActors!]!
# since MovieActors joins distinct types, type name alone is sufficiently precise
actors_via_MovieActors: [Actor!]!
}
extend type Actor {
movieActors: [MovieActors!]! # standard many-to-one relation to join table
movies: [Movie!]! # many-to-many via join table
movieActors_on_movie: [MovieActors!]!
movies_via_MovieActors: [Movie!]!
}
다대다 쿼리
별칭이 있는 쿼리를 살펴보고 _via_
문법을 설명해 보겠습니다.
query GetMovieCast($movieId: UUID!, $actorId: UUID!) @auth(level: PUBLIC) {
movie(id: $movieId) {
mainActors: actors_via_MovieActor(where: { role: { eq: "main" } }) {
name
}
supportingActors: actors_via_MovieActor(
where: { role: { eq: "supporting" } }
) {
name
}
}
actor(id: $actorId) {
mainRoles: movies_via_MovieActor(where: { role: { eq: "main" } }) {
title
}
supportingRoles: movies_via_MovieActor(
where: { role: { eq: "supporting" } }
) {
title
}
}
}
집계 쿼리
집계란 무엇이며 왜 사용해야 하나요?
집계 필드를 사용하면 결과 목록에 대한 계산을 실행할 수 있습니다. 집계 필드를 사용하면 다음과 같은 작업을 할 수 있습니다.
- 리뷰의 평균 점수 구하기
- 장바구니에 있는 상품의 총 비용 확인
- 평점이 가장 높거나 가장 낮은 제품 찾기
- 매장의 제품 수 집계
집계는 서버에서 실행되므로 클라이언트 측에서 계산하는 것보다 여러 가지 이점이 있습니다.
- 클라이언트 측 계산을 피하므로 앱 성능이 향상됨
- 데이터 이그레스 비용 감소 (모든 입력 대신 집계된 결과만 전송하므로)
- 보안 강화 (전체 데이터 세트 대신 집계된 데이터에 대한 클라이언트 액세스 권한을 부여할 수 있음)
집계 스키마 예시
이 섹션에서는 집계를 사용하는 방법을 설명하는 데 적합한 스토어 샘플 스키마로 전환합니다.
type Product @table {
name: String!
manufacturer: String!
quantityInStock: Int!
price: Float!
expirationDate: Date
}
간단한 집계
모든 필드의 _count
가장 간단한 집계 필드는 _count
입니다. 쿼리와 일치하는 행 수를 반환합니다. 유형의 각 필드에 대해 Data Connect는 필드 유형에 따라 상응하는 집계 필드를 생성합니다.
쿼리
query CountProducts {
products {
_count
}
}
응답 one
one
예를 들어 데이터베이스에 제품이 5개 있는 경우 결과는 다음과 같습니다.
{
"products": [
{
"_count": 5
}
]
}
모든 필드에는 해당 필드에 null이 아닌 값이 있는 행 수를 계산하는 <field>_count
필드가 있습니다.
쿼리
query CountProductsWithExpirationDate {
products {
expirationDate_count
}
}
응답field_count
field_count
예를 들어 만료일이 있는 제품이 3개 있는 경우 결과는 다음과 같습니다.
{
"products": [
{
"expirationDate_count": 3
}
]
}
숫자 필드의 _min, _max, _sum, _avg
숫자 필드 (int, float, int64)에도 <field>_min
, <field>_max
, <field>_sum
, <field>_avg
가 있습니다.
쿼리
query NumericAggregates {
products {
quantityInStock_max
price_min
price_avg
quantityInStock_sum
}
}
응답_min _max _sum _avg
_min _max _sum _avg
예를 들어 다음과 같은 제품이 있다고 가정해 보겠습니다.
- 제품 A:
quantityInStock: 10
,price: 2.99
- 제품 B:
quantityInStock: 5
,price: 5.99
- 제품 C:
quantityInStock: 20
,price: 1.99
결과는 다음과 같습니다.
{
"products": [
{
"quantityInStock_max": 20,
"price_min": 1.99,
"price_avg": 3.6566666666666666,
"quantityInStock_sum": 35
}
]
}
날짜 및 타임스탬프의 _min 및 _max
날짜 및 타임스탬프 필드에는 <field>_min
및 <field>_max
가 있습니다.
쿼리
query DateAndTimeAggregates {
products {
expirationDate_max
expirationDate_min
}
}
응답_min _maxdatetime
_min _maxdatetime
예를 들어 다음과 같은 만료일이 있다고 가정해 보겠습니다.
- 제품 A:
2024-01-01
- 제품 B:
2024-03-01
- 제품 C:
2024-02-01
결과는 다음과 같습니다.
{
"products": [
{
"expirationDate_max": "2024-03-01",
"expirationDate_min": "2024-01-01"
}
]
}
Distinct
distinct
인수를 사용하면 필드(또는 필드 조합)의 모든 고유한 값을 가져올 수 있습니다. 예를 들면 다음과 같습니다.
쿼리
query ListDistinctManufacturers {
products(distinct: true) {
manufacturer
}
}
응답distinct
distinct
예를 들어 다음과 같은 제조업체가 있는 경우
- 제품 A:
manufacturer: "Acme"
- 제품 B:
manufacturer: "Beta"
- 제품 C:
manufacturer: "Acme"
결과는 다음과 같습니다.
{
"products": [
{ "manufacturer": "Acme" },
{ "manufacturer": "Beta" }
]
}
집계 필드에서 distinct
인수를 사용하여 고유한 값을 집계할 수도 있습니다. 예를 들면 다음과 같습니다.
쿼리
query CountDistinctManufacturers {
products {
manufacturer_count(distinct: true)
}
}
응답distinctonaggregate
distinctonaggregate
예를 들어 다음과 같은 제조업체가 있는 경우
- 제품 A:
manufacturer: "Acme"
- 제품 B:
manufacturer: "Beta"
- 제품 C:
manufacturer: "Acme"
결과는 다음과 같습니다.
{
"products": [
{
"manufacturer_count": 2
}
]
}
그룹화된 집계
유형에서 집계 필드와 비집계 필드를 혼합하여 선택하여 그룹화된 집계를 실행합니다. 이렇게 하면 집계되지 않는 필드의 값이 동일한 일치하는 모든 행을 그룹화하고 해당 그룹의 집계 필드를 계산합니다. 예를 들면 다음과 같습니다.
쿼리
query MostExpensiveProductByManufacturer {
products {
manufacturer
price_max
}
}
응답groupedaggregates
groupedaggregates
예를 들어 다음과 같은 제품이 있다고 가정해 보겠습니다.
- 제품 A:
manufacturer: "Acme"
,price: 2.99
- 제품 B:
manufacturer: "Beta"
,price: 5.99
- 제품 C:
manufacturer: "Acme"
,price: 1.99
결과는 다음과 같습니다.
{
"products": [
{ "manufacturer": "Acme", "price_max": 2.99 },
{ "manufacturer": "Beta", "price_max": 5.99 }
]
}
그룹화된 집계가 포함된 having
및 where
having
및 where
인수를 사용하여 제공된 기준을 충족하는 그룹만 반환할 수도 있습니다.
having
를 사용하면 집계 필드로 그룹을 필터링할 수 있습니다.where
를 사용하면 집계되지 않은 필드를 기준으로 행을 필터링할 수 있습니다.
쿼리
query FilteredMostExpensiveProductByManufacturer {
products(having: {price_max: {ge: 2.99}}) {
manufacturer
price_max
}
}
응답havingwhere
havingwhere
예를 들어 다음과 같은 제품이 있다고 가정해 보겠습니다.
- 제품 A:
manufacturer: "Acme"
,price: 2.99
- 제품 B:
manufacturer: "Beta"
,price: 5.99
- 제품 C:
manufacturer: "Acme"
,price: 1.99
결과는 다음과 같습니다.
{
"products": [
{ "manufacturer": "Acme", "price_max": 2.99 },
{ "manufacturer": "Beta", "price_max": 5.99 }
]
}
여러 테이블에서 집계
집계 필드는 생성된 일대다 관계 필드와 함께 사용하여 데이터에 관한 복잡한 질문에 답변할 수 있습니다. 다음은 예시에서 사용할 수 있는 수정된 스키마로, 별도의 테이블 Manufacturer
이 있습니다.
type Product @table {
name: String!
manufacturer: Manufacturer!
quantityInStock: Int!
price: Float!
expirationDate: Date
}
type Manufacturer @table {
name: String!
headquartersCountry: String!
}
이제 집계 필드를 사용하여 제조업체가 생산하는 제품 수를 확인하는 등의 작업을 할 수 있습니다.
쿼리
query GetProductCount($id: UUID) {
manufacturers {
name
products_on_manufacturer {
_count
}
}
}
응답 aggregatesacrosstables
aggregatesacrosstables
예를 들어 다음과 같은 제조업체가 있는 경우
- 제조업체 A:
name: "Acme"
,products_on_manufacturer: 2
- 제조업체 B:
name: "Beta"
,products_on_manufacturer: 1
결과는 다음과 같습니다.
{
"manufacturers": [
{ "name": "Acme", "products_on_manufacturer": { "_count": 2 } },
{ "name": "Beta", "products_on_manufacturer": { "_count": 1 } }
]
}
영화 리뷰 데이터베이스의 변형
앞서 언급한 대로 스키마에서 테이블을 정의하면 Data Connect는 각 테이블에 대해 기본 암시적 변형을 생성합니다.
type Movie @table { ... }
extend type Mutation {
# Insert a row into the movie table.
movie_insert(...): Movie_Key!
# Upsert a row into movie."
movie_upsert(...): Movie_Key!
# Update a row in Movie. Returns null if a row with the specified id/key does not exist
movie_update(...): Movie_Key
# Update rows based on a filter in Movie.
movie_updateMany(...): Int!
# Delete a single row in Movie. Returns null if a row with the specified id/key does not exist
movie_delete(...): Movie_Key
# Delete rows based on a filter in Movie.
movie_deleteMany(...): Int!
}
이를 통해 점점 더 복잡해지는 핵심 CRUD 사례를 구현할 수 있습니다. 5번 빠르게 외쳐보세요.
@transaction
지시어
이 디렉티브는 변형이 항상 데이터베이스 트랜잭션에서 실행되도록 적용합니다.
@transaction
를 사용한 변형은 완전히 성공하거나 완전히 실패합니다. 트랜잭션 내 필드 중 하나라도 실패하면 전체 트랜잭션이 롤백됩니다. 클라이언트 관점에서 모든 실패는 요청 오류로 전체 요청이 실패하고 실행이 시작되지 않은 것처럼 동작합니다.
@transaction
가 없는 변형은 각 루트 필드를 차례로 하나씩 실행합니다. 오류를 부분 필드 오류로 표시하지만 후속 실행의 영향은 표시하지 않습니다.
만들기
기본 생성을 해 보겠습니다.
# Create a movie based on user input
mutation CreateMovie($title: String!, $releaseYear: Int!, $genre: String!, $rating: Int!) {
movie_insert(data: {
title: $title
releaseYear: $releaseYear
genre: $genre
rating: $rating
})
}
# Create a movie with default values
mutation CreateMovie2 {
movie_insert(data: {
title: "Sherlock Holmes"
releaseYear: 2009
genre: "Mystery"
rating: 5
})
}
upsert를 사용합니다.
# Movie upsert using combination of variables and literals
mutation UpsertMovie($title: String!) {
movie_upsert(data: {
title: $title
releaseYear: 2009
genre: "Mystery"
rating: 5
genre: "Mystery/Thriller"
})
}
업데이트 실행
다음은 업데이트 내용입니다. 제작자와 감독은 이러한 평균 시청률이 추세에 맞기를 바랍니다.
movie_update
필드에는 레코드를 식별하는 예상 id
인수와 이 업데이트에서 값을 설정하는 데 사용할 수 있는 data
필드가 포함됩니다.
mutation UpdateMovie(
$id: UUID!,
$genre: String!,
$rating: Int!,
$description: String!
) {
movie_update(id: $id,
data: {
genre: $genre
rating: $rating
description: $description
})
}
여러 업데이트를 실행하려면 movie_updateMany
필드를 사용하세요.
# Multiple updates (increase all ratings of a genre)
mutation IncreaseRatingForGenre($genre: String!, $rating: Int!) {
movie_updateMany(
where: { genre: { eq: $genre } },
data:
{
rating: $rating
})
}
_update
를 사용하여 증분, 감소, 추가, 접두어 추가 작업 사용
_update
및 _updateMany
변형에서는 data:
에서 값을 명시적으로 설정할 수 있지만, 값을 업데이트하려면 증분과 같은 연산자를 적용하는 것이 더 적절할 때가 많습니다.
이전 업데이트 예시를 수정하려면 특정 영화의 평점을 올리려고 한다고 가정해 보겠습니다. inc
연산자와 함께 rating_update
구문을 사용할 수 있습니다.
mutation UpdateMovie(
$id: UUID!,
$ratingIncrement: Int!
) {
movie_update(id: $id, data: {
rating_update: {
inc: $ratingIncrement
}
})
}
Data Connect는 필드 업데이트에 다음 연산자를 지원합니다.
inc
:Int
,Int64
,Float
데이터 유형을 증가시킵니다.dec
:Int
,Int64
,Float
데이터 유형을 감소시킵니다.
append
: 벡터 목록을 제외한 목록 유형에 추가prepend
: 벡터 목록을 제외한 목록 유형 앞에 추가
삭제 실행
물론 영화 데이터를 삭제할 수도 있습니다. 영화 보존론자들은 필름을 최대한 오래 보존하고 싶어 할 것입니다.
# Delete by key
mutation DeleteMovie($id: UUID!) {
movie_delete(id: $id)
}
여기에서 _deleteMany
를 사용할 수 있습니다.
# Multiple deletes
mutation DeleteUnpopularMovies($minRating: Int!) {
movie_deleteMany(where: { rating: { le: $minRating } })
}
관계에 변형 쓰기
관계에서 암시적 _upsert
변형을 사용하는 방법을 살펴봅니다.
# Create or update a one to one relation
mutation MovieMetadataUpsert($movieId: UUID!, $director: String!) {
movieMetadata_upsert(
data: { movie: { id: $movieId }, director: $director }
)
}
Data Connect가 field_expr
문법을 사용하여 값을 제공하도록 허용
키 스칼라 및 서버 값에서 설명한 대로 서버가 클라이언트 요청에 응답하여 id
및 날짜와 같은 공통 필드의 값을 채우도록 스키마를 설계할 수 있습니다.
또한 클라이언트 앱의 Data Connect request
객체로 전송된 사용자 ID와 같은 데이터를 활용할 수 있습니다.
변형을 구현할 때는 field_expr
문법을 사용하여 서버에서 생성된 업데이트를 트리거하거나 요청의 데이터에 액세스합니다. 예를 들어 요청에 저장된 승인 uid
를 _upsert
작업에 전달하려면 userId_expr
필드에 "auth.uid"
를 전달합니다.
# Add a movie to the user's favorites list
mutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) {
favorite_movie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId })
}
# Remove a movie from the user's favorites list
mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) {
favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}
또는 익숙한 할 일 목록 앱에서 새 할 일 목록을 만들 때 id_expr
를 전달하여 서버에 목록의 UUID를 자동으로 생성하도록 지시할 수 있습니다.
mutation CreateTodoListWithFirstItem(
$listName: String!
) @transaction {
# Step 1
todoList_insert(data: {
id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
name: $listName,
})
}
자세한 내용은 스칼라 참조의 _Expr
스칼라를 참고하세요.
승인 데이터 조회 쿼리
먼저 데이터베이스를 쿼리하고 CEL 표현식으로 쿼리 결과를 확인하여 Data Connect 변형을 승인할 수 있습니다. 예를 들어 테이블에 쓰고 있는데 다른 테이블의 행 내용을 확인해야 하는 경우에 유용합니다.
이 기능은 다음을 지원합니다.
- 필드의 콘텐츠를 평가하고 이러한 평가 결과에 따라 다음을 실행할 수 있는
@check
지시문:- 변형으로 정의된 생성, 업데이트, 삭제 진행
- 쿼리 결과 반환 진행
- 반환된 값을 사용하여 클라이언트 코드에서 다른 로직 실행
- 와이어 프로토콜 결과에서 쿼리 결과를 생략할 수 있는
@redact
디렉티브
이러한 기능은 승인 흐름에 유용합니다.
상응하는 SQL 스키마
-- Movies Table
CREATE TABLE Movies (
movie_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
title VARCHAR(255) NOT NULL,
release_year INT,
genre VARCHAR(30),
rating INT,
description TEXT,
tags TEXT[]
);
-- Movie Metadata Table
CREATE TABLE MovieMetadata (
movie_id UUID REFERENCES Movies(movie_id) UNIQUE,
director VARCHAR(255) NOT NULL,
PRIMARY KEY (movie_id)
);
-- Actors Table
CREATE TABLE Actors (
actor_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
name VARCHAR(30) NOT NULL
);
-- MovieActor Join Table for Many-to-Many Relationship
CREATE TABLE MovieActor (
movie_id UUID REFERENCES Movies(movie_id),
actor_id UUID REFERENCES Actors(actor_id),
role VARCHAR(50) NOT NULL, # "main" or "supporting"
PRIMARY KEY (movie_id, actor_id),
FOREIGN KEY (movie_id) REFERENCES Movies(movie_id),
FOREIGN KEY (actor_id) REFERENCES Actors(actor_id)
);
-- Users Table
CREATE TABLE Users (
user_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_auth VARCHAR(255) NOT NULL
username VARCHAR(30) NOT NULL
);
-- Reviews Table
CREATE TABLE Reviews (
review_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
user_id UUID REFERENCES Users(user_id),
movie_id UUID REFERENCES Movies(movie_id),
rating INT,
review_text TEXT,
review_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE (movie_id, user_id)
FOREIGN KEY (user_id) REFERENCES Users(user_id),
FOREIGN KEY (movie_id) REFERENCES Movies(movie_id)
);
-- Self Join Example for Movie Sequel Relationship
ALTER TABLE Movies
ADD COLUMN sequel_to UUID REFERENCES Movies(movie_id);
다음 단계
- 승인 및 증명을 사용하여 쿼리와 변형을 안전하게 만드는 방법을 알아보세요.
- 자동 생성된 웹 SDK, Android SDK, iOS SDK, Flutter SDK에서 쿼리와 변형을 호출하는 방법을 알아보세요.