Postgres 배열 및 PHP - 섹션 1

며칠 전에 나는 한 그룹의 음악가를 위해 데이터베이스 모델을 설계했고 모든 사람들이 한 가지 또는 여러 가지 악기를 연주하는 것을 발견했다.결과는 이렇다.
CREATE TABLE musicians
(
    id bigserial NOT NULL,
    name text NOT NULL,
    PRIMARY KEY (id)
);

CREATE TYPE musical_instrument AS ENUM
    ('guitar', 'piano', 'bass', 'trumpet', 'drums');

CREATE TABLE musician_instruments
(
    musician_id bigint NOT NULL,
    instrument musical_instrument NOT NULL,
    "position" integer NOT NULL,
    PRIMARY KEY (musician_id, instrument)
);
보시다시피 표musician_instruments는 악기와 음악가를 연결하는 데 쓰입니다.기기의 순서가 응용 프로그램과 관련이 있기 때문에 position라는 추가 열이 하나 더 있다.
내가 힘껏 눈을 가늘게 떴을 때, 나는 내 앞에 간단한 진열 위장 탁자가 있는 것을 보았다.그래서 왜 Postgres가 제공하는 그룹 형식을 사용하지 않고 나를 어디로 데려갈지 보고 싶다.연구 프로젝트 하나, 가능하다면.
ALTER TABLE public.musicians
    ADD COLUMN instruments musical_instrument[] NOT NULL;

PHP에서 Postgres 배열 사용


사진작가Aleks Magnusson출처Pexels
데이터베이스에 접근하는 응용 프로그램은 Laravel 프레임워크를 PHP로 작성하고 이 프레임워크는 PDO 를 데이터베이스와 대화하는 메커니즘으로 사용한다.이 시계를 조회해서 무슨 일이 일어날지 봅시다.
이것은 musicians 표입니다.
신분증
이름
계기
일.
피터
기타, 피아노
이것은 쿼리입니다.
$musicians = DB::select('SELECT * FROM musicians');
var_dump($musicians);
이것이 바로 우리가 얻은 것이다.
array(1) {
  [0] =>
  class stdClass#728 (3) {
    public $id =>
    int(1)
    public $name =>
    string(5) "Peter"
    public $instruments =>
    string(14) "{guitar,piano}"
  }
}
사진작가Radovan Zierik출처Pexels
심심했어1초 동안 나는 PDO가 이 도구들을 PHP 수조로 되돌려 줄 수 있기를 희망했다. 모든 것이 준비되었다.다행히도 문자열은 해석하기 쉬워 보인다.
function maybeParseArrayOutput(string $output) {
    if ($output === '{}') {
        return [];
    }
    return mb_split(',', mb_substr($output, 1, -1));
}
$instruments = maybeParseArrayOutput('{guitar,piano}');
// ['guitar', 'piano']
이것은 수조의 서브집합에 적용됩니다. 만약 수조가 이 서브집합에 속한다는 것을 알고 있다면, 시작할 수 있습니다.그러나 플러그인 그룹과 복잡한 유형의 그룹을 제외하고는 흔히 볼 수 있는 상황에서 이런 간단한 해결 방안은 고장이 날 수 있다.

Postgres 배열 출력 구문


Postgres가 배열 출력을 포맷하는 방법을 살펴보겠습니다.

The array output routine will put double quotes around element values if they are empty strings, contain curly braces, delimiter characters, double quotes, backslashes, or white space, or match the word NULL. Double quotes and backslashes embedded in element values will be backslash-escaped.

https://www.postgresql.org/docs/current/arrays.html#ARRAYS-IO


규칙은 보기에는 매우 간단하지만, 우리는 정말 해상도를 작성하고 싶습니까?우리는 일부 변두리 상황을 소홀히 하기 쉬우며, 우리의 응용 프로그램에 잠재적인 이용 가능한 빈틈이 존재한다.모험을 좋아하는 독자들에게 Postgres가 되돌아오는 모든 가능성text[]을 처리하는 해상도를 작성하는 것은 도전이다.
다행히도 함수array_to_json를 이용하여 Postgres 수조를 JSON으로 변환하는 더 실용적인 해결 방안이 있습니다. 이것은 PHP가 이 컴퓨터에서 해석할 수 있는 내용입니다.
$musicians = DB::select('SELECT id, name, array_to_json(instruments) as instruments FROM musicians');
foreach ($musicians as $musician) {
    $musician->instruments = json_decode($musician->instruments);
}
var_dump($musicians);
array(1) {
  [0] =>
  class stdClass#2032 (3) {
    public $id =>
    int(1)
    public $name =>
    string(5) "Peter"
    public $instruments =>
    array(2) {
      [0] =>
      string(6) "guitar"
      [1] =>
      string(5) "piano"
    }
  }
}
반대로 우리는 신기록을 삽입할 때 JSON 수조를 text[] 로 변환할 수 있다.
DB::insert(<<<SQL
    INSERT INTO musicians (name, instruments) VALUES
        ('Mike', ARRAY(SELECT * FROM json_array_elements_text(?))::musical_instrument[]);
SQL
, [
    json_encode(['drums', 'bass']),
]);

그럼 포인트가 뭐예요?


사진작가László Virág출처Pexels
우리는 이미 데이터를 JSON으로 변환하고 있는데, 왜 그것을 JSON 열에 저장하지 않습니까?실제로 Laravel과 잘 통합된 실용적인 솔루션을 원한다면 데이터를 JSON으로 직접 저장하는 것이 좋습니다.물론, 균형으로서 원생 Postgres 수조의 강력한 형식 검사와 문서 특성을 잃었습니다.
하지만 우리는 아직 이 생각을 포기하지 말아야 한다.우선, Postgres 수조와 Laravel 검색 생성기를 결합하여 사용하는 방법을 알아보겠습니다.

Laravel 쿼리 생성기가 있는 Postgres 배열


예상한 바와 같이, 검색 생성기를 사용하여 선택하는 데는 문제가 없습니다.
DB::table('musicians')->select([
    'id',
    'name',
    DB::raw('array_to_json(instruments) as instruments'),
]);
다른 한편, 새 줄을 삽입하는 것이 첫 번째 주요 장애물이다.
DB::table('musicians')->insert([
    'name' => 'Paula',
    'instruments' => DB::raw('ARRAY(SELECT * FROM json_array_elements_text(?))::musical_instrument[]')
]);
Laravel에는 자리 표시자 ? 를 채우기 위한 내장 메커니즘이 없습니다.첫 번째 반응은 insert의 원본 코드를 보고 우리가 어디에서 어떤 방식으로 그것을 확장할 수 있는지 확인하는 것이다.함수의 끝까지 스크롤하면 cleanBindings 에 대한 호출이 최종적으로 우리의 값을 귀속에 비추는 것을 볼 수 있습니다.
// From Illuminate\Database\Query\Builder

public function insert(array $values)
{
    // [...]
    return $this->connection->insert(
        $this->grammar->compileInsert($this, $values),
        $this->cleanBindings(Arr::flatten($values, 1))
    );
}

public function cleanBindings(array $bindings)
{
    return array_values(array_filter($bindings, function ($binding) {
        return ! $binding instanceof Expression;
    }));
}
우리는 수조의 값을 입력하여 귀속시켰는데, 그 표현식은 뚜렷한 이유로 필터되었다.우리의 문제에 대해 우리는 상반된 일을 해야 한다.우리는 추가 귀속을 제공하기 위해 표현식이 필요하다.이를 위해서는 먼저 Expression 클래스를 확장해야 합니다.
use Illuminate\Database\Query\Expression;

class ParameterizedExpression extends Expression
{
    protected $bindings;

    public function __construct($value, array $bindings)
    {
        parent::__construct($value);
        $this->bindings = $bindings;
    }

    public function getBindings()
    {
        return $this->bindings;
    }
}
매개 변수화 표현식에서 귀속을 추출하기 위해 cleanBindings 을 수정할 수 있습니다.
public function cleanBindings(array $bindings)
{
    $items = [];
    foreach ($bindings as $binding) {
        if ($binding instanceof ParameterizedExpression) {
            foreach ($binding->getBindings() as $b) {
                $items[] = $b;
            }
        } else if (!($binding instanceof Expression)) {
            $items[] = $binding;
        }
    }
    return $items;
}
이 함수가 있으면 insert 함수를 다음 그룹에 사용할 수 있습니다.
DB::table('musicians')->insert([
    'name' => 'Paula',
    'instruments' => new ParameterizedExpression('ARRAY(SELECT * FROM json_array_elements_text(?))::musical_instrument[]', [json_encode(['trumpet'])]),
]);
그것은 작동할 수 있지만, 만약 우리가 모든 샘플 파일을 보조 함수로 재구성한다면, 우리는 그것을 더욱 깨끗하게 할 수 있다.
class Expr {
    public static function array(array $items, ?string $elementType = null): ParameterizedExpression
    {
        $sql = 'ARRAY(SELECT * FROM json_array_elements_text(?))';
        if ($elementType !== null) {
            $sql .= "::{$elementType}[]";
        }
        return new ParameterizedExpression($sql, [json_encode($items)]);
    }
}

DB::table('musicians')->insert([
    'name' => 'Paula',
    'instruments' => Expr::array(['trumpet'], 'musical_instrument'),
]);
여전히 그룹 형식을 지정해야 하지만, 나는 받아들일 수 있으며, text 그룹을 사용할 때, 그것을 완전히 무시할 수 있다.이 해결 방안의 주요 장점 중 하나는 우리가 insert 를 위해 매개 변수화 표현식을 사용할 수 있을 뿐만 아니라, update 과 모든 다른 변체에 대해 매개 변수화 표현식을 사용할 수 있다는 것이다.가장 큰 단점은 다시 쓰기cleanBindings가 대량의 작업과 관련이 있다는 것이다. 왜냐하면 Builder류에 대한 의존 관계는 체인의 깊은 곳에서 하드코딩되기 때문이다.이것은 거의 엉망진창이다. 충분히 보장할 수 있다composer shenanigans.

끝내다


지금까지, 현재 PHP 라이브러리 상태의 Postgres 수조를 사용하는 것이 나쁜 생각이라고 확신하지 않는다면, 두 번째 부분을 기대할 수 있습니다. 그 중에서 Postgres 수조와 웅변의 모델을 사용해 보겠습니다.
너희는github에서 repository 과 이 블로그의 코드 예시를 찾을 수 있다.
게시물Postgres Arrays and PHP -Part 1이 먼저 hbgl에 올라왔다.

좋은 웹페이지 즐겨찾기