SQL Connect 변이 구현

Firebase SQL Connect를 사용하면 Google Cloud SQL로 관리되는 PostgreSQL 인스턴스의 커넥터를 만들 수 있습니다. 이러한 커넥터는 스키마의 데이터를 사용하는 쿼리 및 변형의 조합입니다.

시작하기 가이드에서는 PostgreSQL용 영화 리뷰 앱 스키마를 소개했습니다.

이 가이드에서는 변형을 비롯한 배포 가능한 관리 작업과 임시 관리 작업도 소개했습니다.

  • 배포 가능한 변형 은 정의한 API 엔드포인트가 있는 커넥터의 클라이언트 앱에서 호출하기 위해 구현하는 변형입니다. SQL Connect는 인증 및 승인을 이러한 변형에 통합하고 API를 기반으로 클라이언트 SDK를 생성합니다.
  • 임시 관리 변형 은 권한이 있는 환경에서 실행되어 테이블을 채우고 관리합니다. Firebase 콘솔에서, Firebase Admin SDK을 사용하는 권한이 있는 환경에서, SQL Connect VS Code 확장 프로그램을 사용하는 로컬 개발 환경에서 이러한 변형을 만들고 실행할 수 있습니다.

이 가이드에서는 배포 가능한 변형 을 자세히 살펴봅니다.

변형의 기능SQL Connect

SQL Connect를 사용하면 PostgreSQL 데이터베이스가 제공되는 모든 예상되는 방식으로 기본 변형을 실행할 수 있습니다.

  • CRUD 작업 실행
  • 트랜잭션으로 다단계 작업 관리

하지만 SQL Connect의 GraphQL 확장 프로그램을 사용하면 더 빠르고 효율적인 앱을 위한 고급 변형을 구현할 수 있습니다.

  • 많은 작업에서 반환되는 키 스칼라 를 사용하여 레코드에 대한 반복 작업을 간소화합니다.
  • 서버 값 을 사용하여 서버에서 제공하는 작업으로 데이터를 채웁니다.
  • 다단계 변형 작업 과정에서 쿼리를 실행하여 데이터를 조회하고 코드 줄과 서버로의 왕복을 저장합니다.

생성된 필드를 사용하여 변형 구현

SQL Connect 작업은 스키마의 유형 및 유형 관계를 기반으로 SQL Connect에서 자동으로 생성하는 필드 집합 을 확장합니다. 이러한 필드는 스키마를 수정할 때마다 로컬 도구에서 생성됩니다.

생성된 필드를 사용하여 단일 테이블의 개별 레코드 생성, 업데이트, 삭제부터 더 복잡한 다중 테이블 업데이트까지 변형을 구현할 수 있습니다.

스키마에 Movie 유형과 연결된 Actor 유형이 있다고 가정합니다. SQL Connectmovie_insert, movie_update, movie_delete 필드를 생성합니다.


movie_insert 필드를 사용한 변형

movie_insert 필드는 Movie 테이블에서 단일 레코드를 만드는 변형을 나타냅니다.

이 필드를 사용하여 단일 영화를 만듭니다.

mutation CreateMovie($data: Movie_Data!) {
  movie_insert(data: $data) { key }
}


movie_update 필드를 사용한 변형

movie_update 필드는 Movie 테이블에서 단일 레코드를 업데이트하는 변형을 나타냅니다.

이 필드를 사용하여 키별로 단일 영화를 업데이트합니다.

mutation UpdateMovie($myKey: Movie_Key!, $data: Movie_Data!) {
  movie_update(key: $myKey, data: $data) { key }
}


movie_delete 필드를 사용한 변형

movie_delete 필드는 Movie 테이블에서 단일 레코드를 삭제하는 변형을 나타냅니다.

이 필드를 사용하여 키별로 단일 영화를 삭제합니다.

  mutation DeleteMovie($myKey: Movie_Key!) {
    movie_delete(key: $myKey) { key }
  }

변형의 필수 요소

SQL Connect 변형은 SQL Connect 확장 프로그램이 있는 GraphQL 변형입니다. 일반 GraphQL 변형과 마찬가지로 작업 이름과 GraphQL 변수 목록을 정의할 수 있습니다.

SQL Connect지시어와 같은 맞춤설정된 GraphQL 쿼리를 @auth@transaction로 확장합니다.

따라서 다음 변형에는 다음이 포함됩니다.

  • mutation 유형 정의
  • SignUp 작업 (변형) 이름
  • 단일 변수 $username 작업 인수
  • 단일 지시어 @auth
  • 단일 필드 user_insert.
mutation SignUp($username: String!) @auth(level: USER) {
  user_insert(data: {
    id_expr: "auth.uid"
    username: $username
  })
}

모든 변형 인수에는 유형 선언, String과 같은 기본 제공 유형 또는 Movie와 같은 스키마 정의 맞춤 유형이 필요합니다.

기본 변형 작성

변형 작성을 시작하여 데이터베이스에서 개별 레코드를 만들고 업데이트하고 삭제할 수 있습니다.

만들기

기본 만들기를 실행해 보겠습니다.

# 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
    }
  })
}

SQL Connect는 필드 업데이트를 위해 다음 연산자를 지원합니다.

  • inc : Int, Int64, Float, Date, Timestamp 데이터 유형을 증가시킵니다.
  • decInt, Int64, Float, Date, Timestamp 데이터 유형을 감소시킵니다.

목록의 경우 다음을 사용하여 개별 값 또는 값 목록으로 업데이트할 수도 있습니다.

  • add: 벡터 목록을 제외한 목록 유형에 항목이 아직 없는 경우 항목을 추가합니다.
  • remove: 벡터 목록을 제외한 목록 유형에서 항목이 있는 경우 모든 항목을 삭제합니다.
  • 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 }
  )
}

효율적인 변형을 위한 스키마 설계

SQL Connect는 더 효율적인 변형을 작성하고 왕복 작업을 저장할 수 있는 두 가지 중요한 기능을 제공합니다.

키 스칼라는 스키마의 키 필드에서 SQL Connect 자동으로 어셈블하는 간결한 객체 식별자입니다. 키 스칼라는 효율성에 관한 것으로, 단일 호출에서 데이터의 ID와 구조에 관한 정보를 찾을 수 있습니다. 특히 새 레코드에 순차적 작업을 실행하고 향후 작업에 전달할 고유 식별자가 필요한 경우, 더 복잡한 작업을 실행하기 위해 관계형 키에 액세스하려는 경우에 유용합니다.

서버 값을 사용하면 저장된 값 또는 쉽게 계산할 수 있는 값을 사용하여 서버에서 테이블의 필드를 동적으로 채울 수 있습니다. 인수의 특정 서버 측 CEL 표현식에 따라expr 예를 들어 작업 요청에 저장된 시간을 사용하여 필드에 액세스할 때 타임스탬프가 적용된 필드를 정의할 수 있습니다(updatedAt: Timestamp! @default(expr: "request.time")).

고급 변형 작성: SQL Connect 문법을 사용하여 값 제공field_expr

키 스칼라 및 서버 값에서 설명한 대로 서버가 클라이언트 요청에 응답하여 id 및 날짜와 같은 일반 필드의 값을 채우도록 스키마를 설계할 수 있습니다.

또한 클라이언트 앱의 SQL Connect request 객체에서 전송된 사용자 ID와 같은 데이터를 사용할 수 있습니다.

변형을 구현할 때는 field_expr 문법을 사용하여 서버에서 생성된 업데이트를 트리거하거나 요청에서 데이터에 액세스합니다. 예를 들어 요청에 저장된 승인 uid_upsert 작업에 전달하려면 "auth.uid"userId_expr 필드에 전달합니다.

# 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 스칼라 참조의 스칼라를 참고하세요.

고급 변형 작성: 다단계 작업

하나의 변형에 여러 쓰기 필드 (예: 삽입)를 포함하려는 경우가 많습니다. 또한 변형을 실행하는 동안 데이터베이스를 읽어 삽입 또는 업데이트를 실행하기 전에 기존 데이터를 조회하고 확인할 수도 있습니다. 이러한 옵션을 사용하면 왕복 작업과 비용을 절약할 수 있습니다.

SQL Connect를 사용하면 다음을 지원하여 변형에서 다단계 로직을 실행할 수 있습니다.

  • 여러 쓰기 필드

  • 변형의 여러 읽기 필드 (query 필드 키워드 사용)

  • 관계형 데이터베이스에서 익숙한 트랜잭션 지원을 제공하는 @transaction 지시어, which provides transaction support

  • CEL 표현식을 사용하여 읽기의 콘텐츠를 평가하고 이러한 평가 결과를 기반으로 다음을 실행할 수 있는 @check 지시어

    • 변형에서 정의한 생성, 업데이트, 삭제를 진행합니다.
    • 쿼리 필드의 결과를 반환합니다.
    • 반환된 메시지를 사용하여 클라이언트 코드에서 적절한 로직을 실행합니다.
  • 와이어 프로토콜 결과에서 쿼리 필드 결과를 생략할 수 있는 @redact 지시어

  • 복잡한 다단계 작업에서 실행된 모든 변형 및 쿼리의 누적 결과를 저장하는 CEL response 결합 response 결합에 액세스할 수 있습니다.

    • expr: 인수를 통해 @check 지시어에서
    • 서버 값으로 field_expr 문법을 사용하여

@transaction 지시어

다단계 변형 지원에는 트랜잭션을 사용한 오류 처리가 포함됩니다.

@transaction 지시어는 단일 쓰기 필드 (예: _insert 또는 _update) 또는 여러 쓰기 필드가 있는 변형이 항상 데이터베이스 트랜잭션에서 실행되도록 합니다.

  • @transaction이 없는 변형은 각 루트 필드를 순서대로 하나씩 실행합니다. 이 작업은 후속 실행의 영향이 아니라 오류를 부분 필드 오류로 표시합니다.

  • @transaction이 있는 변형은 완전히 성공하거나 완전히 실패합니다. 트랜잭션 내의 필드 중 하나라도 실패하면 전체 트랜잭션이 롤백됩니다.

@check@redact 지시어

@check 지시어는 지정된 필드가 쿼리 결과에 있는지 확인합니다. CEL (Common Expression Language) 표현식은 필드 값을 테스트하는 데 사용됩니다. 지시어의 기본 동작은 값이 null 또는 [] (빈 목록)인 노드를 확인하고 거부하는 것입니다.

@redact 지시어는 클라이언트의 응답 일부를 수정합니다. 수정된 필드는 데이터 변경 및 @check를 비롯한 부작용에 대해 계속 평가되며 결과는 CEL 표현식의 후속 단계에서 계속 사용할 수 있습니다.

@check, @check(message:), @redact 사용

@check@redact의 주요 용도는 논리의 조회를 사용하여 특정 작업을 승인해야 하는지 결정하는 것이지만 클라이언트에는 숨기는 것입니다. 쿼리는 클라이언트 코드에서 올바르게 처리하는 데 유용한 메시지를 반환할 수 있습니다.

query GetMovieEditors($movieId: UUID!) @auth(level: USER) {
  moviePermission(key: { movieId: $movieId, userId_expr: "auth.uid" }) @redact {
    role @check(expr: "this == 'admin'", message: "You must be an admin to view all editors of a movie.")
  }
  moviePermissions(where: { movieId: { eq: $movieId }, role: { eq: "editor" } }) {
    user {
      id
      username
    }
  }
}

승인 확인의 @check@redact 지시어에 관해 자세히 알아보려면 승인 데이터 조회에 관한 설명을 참고하세요.

@check를 사용하여 키 검증

_update와 같은 일부 변형 필드는 지정된 키가 있는 레코드가 없는 경우 no-op일 수 있습니다. 마찬가지로 조회는 null 또는 빈 목록을 반환할 수 있습니다. 이러한 항목은 오류로 간주되지 않으므로 롤백을 트리거하지 않습니다.

이 결과를 방지하려면 @check 지시어를 사용하여 키를 찾을 수 있는지 테스트합니다.

# Delete by key, error if not found
mutation MustDeleteMovie($id: UUID!) @transaction {
  movie_delete(id: $id) @check(expr: "this != null", message: "Movie not found, therefore nothing is deleted")
}

response 결합을 사용하여 다단계 변형 연결

관련 레코드(예: 새 Movie 및 연결된 MovieMetadata 항목)를 만드는 기본 접근 방식은 다음과 같습니다.

  1. Movie_insert 변형을 호출합니다.
  2. 생성된 영화의 반환된 키를 저장합니다.
  3. 그런 다음 두 번째 _insert 변형을 호출하여 MovieMetadata 레코드를 만듭니다.

하지만 SQL Connect를 사용하면 두 번째 _insert에서 첫 번째 _insert결과에 액세스하여 이 일반적인 사례를 단일 다단계 작업으로 처리할 수 있습니다.

성공적인 영화 리뷰 앱을 만드는 것은 많은 작업입니다. 새로운 예시로 할 일 목록을 추적해 보겠습니다.

response를 사용하여 서버 값으로 필드 설정

다음 할 일 목록 변형에서

  • response 결합은 현재 결합 이전의 모든 최상위 변형 필드를 포함하는 지금까지의 부분 응답 객체를 나타냅니다.
  • 초기 todoList_insert 작업의 결과는 id (키) 필드를 반환하며, 나중에 response.todoList_insert.id에서 액세스하므로 새 할 일 항목을 즉시 삽입할 수 있습니다.
mutation CreateTodoListWithFirstItem(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Sub-step 1:
  todoList_insert(data: {
    id_expr: "uuidV4()", # <-- auto-generated. Or a column-level @default on `type TodoList` will also work
    name: $listName,
  })
  # Sub-step 2:
  todo_insert(data: {
    listId_expr: "response.todoList_insert.id" # <-- Grab the newly generated ID from the partial response so far.
    content: $itemContent,
  })
}

response를 사용하여 @check로 필드 검증

response@check(expr: "...")에서도 사용할 수 있으므로 이를 사용하여 훨씬 더 복잡한 서버 측 로직을 빌드할 수 있습니다. 변형의 query { … } 단계와 결합하면 추가 클라이언트-서버 왕복 없이 훨씬 더 많은 작업을 실행할 수 있습니다.

다음 예시에서 @check는 연결된 단계 후에 항상 실행되므로 @check가 이미 response.query에 액세스할 수 있습니다.

mutation CreateTodoInNamedList(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Sub-step 1: Look up List.id by its name
  query
  @check(expr: "response.query.todoLists.size() > 0", message: "No such TodoList with the name!")
  @check(expr: "response.query.todoLists.size() < 2", message: "Ambiguous listName!") {
    todoLists(where: { name: $listName }) {
      id
    }
  }
  # Sub-step 2:
  todo_insert(data: {
    listId_expr: "response.todoLists[0].id" # <-- Now we have the parent list ID to insert to
    content: $itemContent,
  })
}

response 결합에 관한 자세한 내용은 CEL 참조를 참고하세요.

@transactionquery @check로 중단된 작업 이해

다단계 변형에서 오류가 발생할 수 있습니다.

  • 데이터베이스 작업이 실패할 수 있습니다.
  • query @check 로직이 작업을 종료할 수 있습니다.

SQL Connect는 다단계 변형과 함께 @transaction 지시어를 사용하는 것이 좋습니다. 이렇게 하면 클라이언트 코드에서 더 쉽게 처리할 수 있는 일관된 데이터베이스 및 변형 결과가 생성됩니다.

  • 첫 번째 오류 또는 실패한 @check에서 작업이 종료되므로 후속 필드의 실행 또는 CEL 평가를 관리할 필요가 없습니다.
  • 롤백은 데이터베이스 오류 또는 @check 로직에 대한 응답으로 실행되어 일관된 데이터베이스 상태를 생성합니다.
  • 롤백 오류는 항상 클라이언트 코드에 반환됩니다.

@transaction을 사용하지 않기로 선택하는 사용 사례가 있을 수 있습니다. 예를 들어 처리량, 확장성 또는 가용성이 더 높은 경우 eventual consistency를 선택할 수 있습니다. 하지만 결과를 허용하려면 데이터베이스와 클라이언트 코드를 관리해야 합니다.

  • 데이터베이스 작업으로 인해 하나의 필드가 실패하면 후속 필드가 계속 실행됩니다. 하지만 실패한 @check는 여전히 전체 작업을 종료합니다.
  • 롤백이 실행되지 않습니다. 즉, 일부 업데이트는 성공하고 일부 업데이트는 실패한 혼합 데이터베이스 상태입니다.
  • @check 로직이 이전 단계에서 읽기 또는 쓰기의 결과를 사용하는 경우 @check를 사용한 작업에서 더 일관성 없는 결과가 발생할 수 있습니다.
  • 클라이언트 코드에 반환되는 결과에는 처리할 성공 및 실패 응답이 더 복잡하게 혼합되어 있습니다.

SQL Connect 변형의 지시어

유형 및 테이블을 정의하는 데 사용하는 지시어 외에도 SQL Connect는 작업의 동작을 보강하기 위해 @auth, @check, @redact@transaction 지시어를 제공합니다.

지시어 적용 대상 설명
@auth 쿼리 및 변형 쿼리 또는 변형의 승인 정책을 정의합니다. 승인 및 증명 가이드를 참고하세요.
@check 다단계 작업의 query 필드 지정된 필드가 쿼리 결과에 있는지 확인합니다. CEL (Common Expression Language) 표현식은 필드 값을 테스트하는 데 사용됩니다. 다단계 작업을 참고하세요 .
@redact 쿼리 클라이언트의 응답 일부를 수정합니다. 다단계 작업을 참고하세요 .
@transaction 변형 변형이 항상 데이터베이스 트랜잭션에서 실행되도록 합니다. 다단계 작업을 참고하세요 .

다음 단계

관심 있을 만한 항목: