Thêm một giá trị mới vào Loại ENUM hiện có


208

Tôi có một cột bảng sử dụng một enumloại. Tôi muốn cập nhật enumloại đó để có thêm một giá trị có thể. Tôi không muốn xóa bất kỳ giá trị hiện có, chỉ cần thêm giá trị mới. Cách đơn giản nhất để làm điều này là gì?

Câu trả lời:


153

LƯU Ý nếu bạn đang sử dụng PostgreSQL 9.1 trở lên và bạn vẫn ổn khi thực hiện các thay đổi bên ngoài giao dịch, hãy xem câu trả lời này để biết cách tiếp cận đơn giản hơn.


Tôi đã có cùng một vấn đề vài ngày trước và tìm thấy bài đăng này. Vì vậy, câu trả lời của tôi có thể hữu ích cho những người đang tìm kiếm giải pháp :)

Nếu bạn chỉ có một hoặc hai cột sử dụng loại enum bạn muốn thay đổi, bạn có thể thử điều này. Ngoài ra, bạn có thể thay đổi thứ tự của các giá trị trong loại mới.

-- 1. rename the enum type you want to change
alter type some_enum_type rename to _some_enum_type;
-- 2. create new type
create type some_enum_type as enum ('old', 'values', 'and', 'new', 'ones');
-- 3. rename column(s) which uses our enum type
alter table some_table rename column some_column to _some_column;
-- 4. add new column of new type
alter table some_table add some_column some_enum_type not null default 'new';
-- 5. copy values to the new column
update some_table set some_column = _some_column::text::some_enum_type;
-- 6. remove old column and type
alter table some_table drop column _some_column;
drop type _some_enum_type;

3-6 nên được lặp lại nếu có nhiều hơn 1 cột.


9
Điều đáng nói là tất cả có thể được thực hiện trong một giao dịch, do đó, hầu như an toàn để thực hiện trong cơ sở dữ liệu sản xuất.
David Leppik

52
Đây chưa bao giờ là một ý tưởng tốt. Kể từ 9.1 bạn có thể làm tất cả với ALTER TYPE. Nhưng ngay cả trước đó, ALTER TABLE foo ALTER COLUMN bar TYPE new_type USING bar::text::new_type;đã vượt trội hơn nhiều.
Erwin Brandstetter

1
Xin lưu ý rằng các phiên bản cũ hơn của Postgres không hỗ trợ đổi tên các loại. Cụ thể, phiên bản Postgres trên Heroku (db được chia sẻ, tôi tin rằng họ sử dụng PG 8.3) không hỗ trợ nó.
Ortwin Gentz

13
Bạn có thể thu gọn các bước 3, 4, 5 và 6 lại thành một tuyên bố duy nhất:ALTER TABLE some_table ALTER COLUMN some_column TYPE some_enum_type USING some_column::text::some_enum_type;
glyphobet

3
Nếu làm điều này trên một bảng trực tiếp, khóa bảng trong khi làm thủ tục. Mức cô lập giao dịch mặc định trong postgresql sẽ không ngăn các hàng mới được chèn bởi các giao dịch khác trong giao dịch này, do đó bạn có thể bị bỏ lại với các hàng được điền sai.
Sérgio Carvalho

421

PostgreQuery 9.1 giới thiệu khả năng ALTER Enum loại:

ALTER TYPE enum_type ADD VALUE 'new_value'; -- appends to list
ALTER TYPE enum_type ADD VALUE 'new_value' BEFORE 'old_value';
ALTER TYPE enum_type ADD VALUE 'new_value' AFTER 'old_value';

1
"enum_type" là gì? tên trường, tên bảng_field? hay cái gì khác? Làm thế nào tôi nên đánh nó? Tôi có bảng "điểm" và tôi có cột "loại" Và trong db dump tôi nhận được điều này: CONSTRAINT lớp_type_check CHECK (((loại) :: text = BẤT K ( : thay đổi ký tự, 'thêm' :: thay đổi ký tự, 'giữa chừng' :: thay đổi ký tự, 'cuối cùng' :: thay đổi ký tự]) :: text [])))

1
enum_type chỉ là một loại tên enum của riêng bạn @mariotanenbaum. Nếu enum của bạn là một "loại" thì đây là những gì bạn nên sử dụng.
Dariusz

26
Có thể loại bỏ một?
Ced

8
Thêm vào nhận xét của @DrewNoakes, nếu bạn đang sử dụng db-di chuyển (chạy trong giao dịch), thì bạn có thể gặp lỗi: ERROR: ALTER TYPE ... ADD không thể chạy trong khối giao dịch Giải pháp được đề cập ở đây (bởi Hubbitus ): stackoverflow.com/a/41696273/1161370
Mahesh

1
bạn không thể xóa nó để không thể di chuyển xuống vì vậy phải sử dụng các phương pháp khác
Muhammad Umer

65

Một giải pháp có thể là như sau; điều kiện tiên quyết là, không có xung đột trong các giá trị enum được sử dụng. (ví dụ: khi xóa giá trị enum, hãy chắc chắn rằng giá trị này không được sử dụng nữa.)

-- rename the old enum
alter type my_enum rename to my_enum__;
-- create the new enum
create type my_enum as enum ('value1', 'value2', 'value3');

-- alter all you enum columns
alter table my_table
  alter column my_column type my_enum using my_column::text::my_enum;

-- drop the old enum
drop type my_enum__;

Cũng theo cách này, thứ tự cột sẽ không được thay đổi.


1
+1 đây là cách để đi trước 9.1 và vẫn là cách để xóa hoặc sửa đổi các yếu tố.

Đây là câu trả lời tốt nhất cho giải pháp của tôi, trong đó thêm các enum mới vào một loại enum hiện có, trong đó chúng ta đang giữ tất cả các enum cũ và thêm các enum mới. Ngoài ra, kịch bản cập nhật của chúng tôi là giao dịch. Bài đăng tuyệt vời!
Darin Peterson

1
Câu trả lời rực rỡ! Tránh các vụ hack xung quanh pg_enumcó thể thực sự phá vỡ mọi thứ và là giao dịch, không giống như ALTER TYPE ... ADD.
NathanAldenSr

4
Trong trường hợp cột của bạn có giá trị mặc định, bạn sẽ nhận được lỗi sau : default for column "my_column" cannot be cast automatically to type "my_enum". Bạn sẽ phải làm như sau: ALTER TABLE "my_table" ALTER COLUMN "my_column" DROP DEFAULT, ALTER COLUMN "my_column" TYPE "my_type" USING ("my_column"::text::"my_type"), ALTER COLUMN "my_column" SET DEFAULT 'my_default_value';
n1ru4l 15/03/19

30

Nếu bạn rơi vào tình huống khi bạn nên thêm enumcác giá trị trong giao dịch, hãy thực hiện nó trong di chuyển đường bay trên ALTER TYPEcâu lệnh, bạn sẽ gặp lỗi ERROR: ALTER TYPE ... ADD cannot run inside a transaction block(xem vấn đề đường bay số 350 ), bạn có thể thêm các giá trị đó vào pg_enumtrực tiếp như cách giải quyết ( type_egais_unitslà tên của mục tiêu enum):

INSERT INTO pg_enum (enumtypid, enumlabel, enumsortorder)
    SELECT 'type_egais_units'::regtype::oid, 'NEW_ENUM_VALUE', ( SELECT MAX(enumsortorder) + 1 FROM pg_enum WHERE enumtypid = 'type_egais_units'::regtype )

9
Tuy nhiên, điều này sẽ yêu cầu cấp quyền quản trị viên, vì nó thay đổi bảng hệ thống.
asnelzin

22

Bổ sung @Dariusz 1

Đối với Rails 4.2.1, có phần tài liệu này:

== Di chuyển giao dịch

Nếu bộ điều hợp cơ sở dữ liệu hỗ trợ các giao dịch DDL, tất cả các lần di chuyển sẽ tự động được gói trong một giao dịch. Tuy nhiên, có những truy vấn mà bạn không thể thực hiện trong một giao dịch và đối với những tình huống này, bạn có thể tắt các giao dịch tự động.

class ChangeEnum < ActiveRecord::Migration
  disable_ddl_transaction!

  def up
    execute "ALTER TYPE model_size ADD VALUE 'new_value'"
  end
end

3
điều này! nếu bạn đang chơi với enums trong đường ray hiện đại, đây chính xác là những gì bạn đang tìm kiếm.
Eli Albert

1
Tuyệt vời, đã giúp tôi rất nhiều!
Dmytro Uhnichenko

10

Từ Tài liệu Postgres 9.1 :

ALTER TYPE name ADD VALUE new_enum_value [ { BEFORE | AFTER } existing_enum_value ]

Thí dụ:

ALTER TYPE user_status ADD VALUE 'PROVISIONAL' AFTER 'NORMAL'

3
Cũng từ tài liệu: So sánh liên quan đến giá trị enum được thêm vào đôi khi sẽ chậm hơn so với so sánh chỉ liên quan đến các thành viên ban đầu của loại enum. [.... chi tiết bị cắt khi quá dài cho nhận xét stackoverflow ...] Sự chậm lại thường không đáng kể; nhưng nếu nó quan trọng, hiệu suất tối ưu có thể được lấy lại bằng cách loại bỏ và tạo lại kiểu enum, hoặc bằng cách đổ và tải lại cơ sở dữ liệu.
Aaron Zinman

8

Tuyên bố từ chối trách nhiệm: Tôi chưa thử giải pháp này, vì vậy nó có thể không hoạt động ;-)

Bạn nên nhìn vào pg_enum . Nếu bạn chỉ muốn thay đổi nhãn của ENUM hiện có, một CẬP NHẬT đơn giản sẽ làm điều đó.

Để thêm một giá trị ENUM mới:

  • Đầu tiên chèn giá trị mới vào pg_enum . Nếu giá trị mới phải là giá trị cuối cùng, bạn đã hoàn thành.
  • Nếu không (bạn cần một giá trị ENUM mới ở giữa các giá trị hiện có), bạn sẽ phải cập nhật từng giá trị riêng biệt trong bảng của mình, từ mức cao nhất đến mức thấp nhất ...
  • Sau đó, bạn sẽ phải đổi tên chúng theo pg_enumthứ tự ngược lại.

Minh họa
Bạn có bộ nhãn sau:

ENUM ('enum1', 'enum2', 'enum3')

và bạn muốn có được:

ENUM ('enum1', 'enum1b', 'enum2', 'enum3')

sau đó:

INSERT INTO pg_enum (OID, 'newenum3');
UPDATE TABLE SET enumvalue TO 'newenum3' WHERE enumvalue='enum3';
UPDATE TABLE SET enumvalue TO 'enum3' WHERE enumvalue='enum2';

sau đó:

UPDATE TABLE pg_enum SET name='enum1b' WHERE name='enum2' AND enumtypid=OID;

Và như thế...



5

Tôi dường như không thể đăng bình luận, vì vậy tôi sẽ chỉ nói rằng việc cập nhật pg_enum hoạt động trong Postgres 8.4. Đối với cách các enum của chúng tôi được thiết lập, tôi đã thêm các giá trị mới vào các loại enum hiện có thông qua:

INSERT INTO pg_enum (enumtypid, enumlabel)
  SELECT typelem, 'NEWENUM' FROM pg_type WHERE
    typname = '_ENUMNAME_WITH_LEADING_UNDERSCORE';

Điều đó hơi đáng sợ, nhưng thật hợp lý khi Postgres thực sự lưu trữ dữ liệu của mình.


1
Câu trả lời chính xác! Chỉ giúp thêm vào một enum mới, nhưng rõ ràng không giải quyết được trường hợp bạn phải đặt hàng lại.
Mahmoud Abdelkader


Cùng với dấu gạch dưới hàng đầu cho tên chữ, chúng cũng phân biệt chữ hoa chữ thường. Tôi gần như mất trí khi cố gắng chọn theo tên chữ từ bảng pg_type.
Mahesh

5

Cập nhật pg_enum hoạt động, cũng như thủ thuật cột trung gian được tô sáng ở trên. Người ta cũng có thể sử dụng ma thuật SỬ DỤNG để thay đổi trực tiếp loại cột:

CREATE TYPE test AS enum('a', 'b');
CREATE TABLE foo (bar test);
INSERT INTO foo VALUES ('a'), ('b');

ALTER TABLE foo ALTER COLUMN bar TYPE varchar;

DROP TYPE test;
CREATE TYPE test as enum('a', 'b', 'c');

ALTER TABLE foo ALTER COLUMN bar TYPE test
USING CASE
WHEN bar = ANY (enum_range(null::test)::varchar[])
THEN bar::test
WHEN bar = ANY ('{convert, these, values}'::varchar[])
THEN 'c'::test
ELSE NULL
END;

Miễn là bạn không có chức năng nào yêu cầu rõ ràng hoặc trả lại enum đó, bạn vẫn ổn. (pssql sẽ khiếu nại khi bạn loại bỏ nếu có.)

Ngoài ra, lưu ý rằng PG9.1 đang giới thiệu câu lệnh ALTER TYPE, sẽ hoạt động trên enums:

http://developer.postgresql.org/pgdocs/postgres/release-9-1-alpha.html


Tài liệu có liên quan cho PostgreSQL 9.1 hiện có thể được tìm thấy tại postgresql.org/docs/9.1/static/sql-altertype.html
W Richt Akkerman

1
ALTER TABLE foo ALTER COLUMN bar TYPE test USING bar::text::new_type;Nhưng phần lớn không liên quan bây giờ ...
Erwin Brandstetter

Tương tự như những gì Erwin nói, ... USING bar::typeđã làm việc cho tôi. Tôi thậm chí không phải xác định ::text.
Daniel Werner

3

Đơn giản nhất: thoát khỏi enums Chúng không dễ dàng sửa đổi, và do đó rất hiếm khi được sử dụng.


2
có lẽ một ràng buộc kiểm tra đơn giản sẽ làm gì?

1
Và chính xác vấn đề lưu trữ giá trị dưới dạng chuỗi là gì?

5
@Grazer: trong 9.1 bạn có thể thêm các giá trị vào enum ( depesz.com/index.php/2010/10/27/ mẹo ) - nhưng bạn vẫn không thể xóa các giá trị cũ.

3
@WillSheppard - Tôi nghĩ rằng về cơ bản là không bao giờ. Tôi nghĩ rằng các loại tùy chỉnh dựa trên văn bản với các ràng buộc kiểm tra sẽ tốt hơn nhiều trong mọi trường hợp.

3
@JackDoumund - chắc chắn rồi. Tôi sẽ lấy tên miền với kiểm tra enum bất cứ ngày nào.

3

Không thể thêm nhận xét vào vị trí thích hợp, nhưng ALTER TABLE foo ALTER COLUMN bar TYPE new_enum_type USING bar::text::new_enum_typevới mặc định trên cột không thành công. Tôi phải:

ALTER table ALTER COLUMN bar DROP DEFAULT;

và sau đó nó hoạt động.


2

chỉ trong trường hợp, nếu bạn đang sử dụng Rails và bạn có một vài câu lệnh, bạn sẽ cần phải thực thi từng câu một, như:

execute "ALTER TYPE XXX ADD VALUE IF NOT EXISTS 'YYY';"
execute "ALTER TYPE XXX ADD VALUE IF NOT EXISTS 'ZZZ';"

1

Đây là một giải pháp tổng quát hơn nhưng hoạt động khá nhanh, ngoài việc thay đổi kiểu chính nó sẽ cập nhật tất cả các cột trong cơ sở dữ liệu bằng cách sử dụng nó. Phương pháp có thể được áp dụng ngay cả khi một phiên bản ENUM mới khác với nhiều nhãn hoặc bỏ lỡ một số nhãn gốc. Mã dưới đây thay thế my_schema.my_type AS ENUM ('a', 'b', 'c')bằng ENUM ('a', 'b', 'd', 'e'):

CREATE OR REPLACE FUNCTION tmp() RETURNS BOOLEAN AS
$BODY$

DECLARE
    item RECORD;

BEGIN

    -- 1. create new type in replacement to my_type
    CREATE TYPE my_schema.my_type_NEW
        AS ENUM ('a', 'b', 'd', 'e');

    -- 2. select all columns in the db that have type my_type
    FOR item IN
        SELECT table_schema, table_name, column_name, udt_schema, udt_name
            FROM information_schema.columns
            WHERE
                udt_schema   = 'my_schema'
            AND udt_name     = 'my_type'
    LOOP
        -- 3. Change the type of every column using my_type to my_type_NEW
        EXECUTE
            ' ALTER TABLE ' || item.table_schema || '.' || item.table_name
         || ' ALTER COLUMN ' || item.column_name
         || ' TYPE my_schema.my_type_NEW'
         || ' USING ' || item.column_name || '::text::my_schema.my_type_NEW;';
    END LOOP;

    -- 4. Delete an old version of the type
    DROP TYPE my_schema.my_type;

    -- 5. Remove _NEW suffix from the new type
    ALTER TYPE my_schema.my_type_NEW
        RENAME TO my_type;

    RETURN true;

END
$BODY$
LANGUAGE 'plpgsql';

SELECT * FROM tmp();
DROP FUNCTION tmp();

Toàn bộ quá trình sẽ chạy khá nhanh, bởi vì nếu thứ tự nhãn vẫn tồn tại, sẽ không có thay đổi dữ liệu thực tế nào xảy ra. Tôi đã áp dụng phương pháp này trên 5 bảng bằng cách sử dụng my_typevà có 50.000−70.000 hàng trong mỗi bảng và toàn bộ quá trình chỉ mất 10 giây.

Tất nhiên, hàm sẽ trả về một ngoại lệ trong trường hợp nếu các nhãn bị thiếu trong phiên bản mới của ENUM được sử dụng ở đâu đó trong dữ liệu, nhưng trong trường hợp như vậy, dù sao cũng nên làm gì đó.


Điều này thực sự có giá trị. Tuy nhiên, vấn đề là với các khung nhìn sử dụng ENUM cũ. Chúng phải được loại bỏ và tái tạo, điều này phức tạp hơn nhiều khi xem xét các quan điểm khác tùy thuộc vào các quan điểm bị loại bỏ. Không nói về các loại hỗn hợp ...
Ondřej Bouda

1

Đối với những người tìm kiếm một giải pháp trong giao dịch, sau đây dường như hoạt động.

Thay vì một ENUM, a DOMAINsẽ được sử dụng trên loại TEXTvới một ràng buộc kiểm tra rằng giá trị nằm trong danh sách được chỉ định của các giá trị được phép (như được đề xuất bởi một số ý kiến). Vấn đề duy nhất là không có ràng buộc nào có thể được thêm vào (và do đó không được sửa đổi) cho một miền nếu nó được sử dụng bởi bất kỳ loại hỗn hợp nào (các tài liệu chỉ nói rằng "cuối cùng sẽ được cải thiện"). Một hạn chế như vậy có thể được xử lý xung quanh, tuy nhiên, bằng cách sử dụng một ràng buộc gọi hàm, như sau.

START TRANSACTION;

CREATE FUNCTION test_is_allowed_label(lbl TEXT) RETURNS BOOL AS $function$
    SELECT lbl IN ('one', 'two', 'three');
$function$ LANGUAGE SQL IMMUTABLE;

CREATE DOMAIN test_domain AS TEXT CONSTRAINT val_check CHECK (test_is_allowed_label(value));

CREATE TYPE test_composite AS (num INT, word test_domain);

CREATE TABLE test_table (val test_composite);
INSERT INTO test_table (val) VALUES ((1, 'one')::test_composite), ((3, 'three')::test_composite);
-- INSERT INTO test_table (val) VALUES ((4, 'four')::test_composite); -- restricted by the CHECK constraint

CREATE VIEW test_view AS SELECT * FROM test_table; -- just to show that the views using the type work as expected

CREATE OR REPLACE FUNCTION test_is_allowed_label(lbl TEXT) RETURNS BOOL AS $function$
    SELECT lbl IN ('one', 'two', 'three', 'four');
$function$ LANGUAGE SQL IMMUTABLE;

INSERT INTO test_table (val) VALUES ((4, 'four')::test_composite); -- allowed by the new effective definition of the constraint

SELECT * FROM test_view;

CREATE OR REPLACE FUNCTION test_is_allowed_label(lbl TEXT) RETURNS BOOL AS $function$
    SELECT lbl IN ('one', 'two', 'three');
$function$ LANGUAGE SQL IMMUTABLE;

-- INSERT INTO test_table (val) VALUES ((4, 'four')::test_composite); -- restricted by the CHECK constraint, again

SELECT * FROM test_view; -- note the view lists the restricted value 'four' as no checks are made on existing data

DROP VIEW test_view;
DROP TABLE test_table;
DROP TYPE test_composite;
DROP DOMAIN test_domain;
DROP FUNCTION test_is_allowed_label(TEXT);

COMMIT;

Trước đây, tôi đã sử dụng một giải pháp tương tự như câu trả lời được chấp nhận, nhưng nó còn lâu mới tốt khi các chế độ xem hoặc chức năng hoặc loại tổng hợp (và đặc biệt là các chế độ xem sử dụng các chế độ xem khác sử dụng ENUM đã sửa đổi ...) được xem xét. Giải pháp được đề xuất trong câu trả lời này dường như hoạt động trong bất kỳ điều kiện nào.

Nhược điểm duy nhất là không có kiểm tra nào được thực hiện trên dữ liệu hiện có khi một số giá trị được phép bị xóa (có thể được chấp nhận, đặc biệt đối với câu hỏi này). ( ALTER DOMAIN test_domain VALIDATE CONSTRAINT val_checkThật không may, một cuộc gọi kết thúc với cùng một lỗi như thêm một ràng buộc mới vào miền được sử dụng bởi một loại hỗn hợp.)

Lưu ý rằng một sửa đổi nhỏ như CHECK (value = ANY(get_allowed_values())), trong đó get_allowed_values()hàm trả về danh sách các giá trị được phép, sẽ không hoạt động - điều này khá lạ, vì vậy tôi hy vọng giải pháp được đề xuất ở trên hoạt động đáng tin cậy (cho đến nay ...). (nó hoạt động, thực sự - đó là lỗi của tôi)


0

Như đã thảo luận ở trên, ALTERlệnh không thể được viết trong một giao dịch. Cách được đề xuất là chèn trực tiếp vào bảng pg_enum, bởi retrieving the typelem from pg_type tablecalculating the next enumsortorder number;

Sau đây là mã mà tôi sử dụng. (Kiểm tra nếu giá trị trùng lặp tồn tại trước khi chèn (ràng buộc giữa tên enumtypid và tên enumlabel)

INSERT INTO pg_enum (enumtypid, enumlabel, enumsortorder)
    SELECT typelem,
    'NEW_ENUM_VALUE',
    (SELECT MAX(enumsortorder) + 1 
        FROM pg_enum e
        JOIN pg_type p
        ON p.typelem = e.enumtypid
        WHERE p.typname = '_mytypename'
    )
    FROM pg_type p
    WHERE p.typname = '_mytypename'
    AND NOT EXISTS (
        SELECT * FROM 
        pg_enum e
        JOIN pg_type p
        ON p.typelem = e.enumtypid
        WHERE e.enumlabel = 'NEW_ENUM_VALUE'
        AND p.typname = '_mytypename'
    )

Lưu ý rằng tên loại của bạn được thêm vào một dấu gạch dưới trong bảng pg_type. Ngoài ra, tên chữ cần phải là chữ thường trong mệnh đề where.

Bây giờ điều này có thể được viết một cách an toàn vào tập lệnh di chuyển db của bạn.


-1

Tôi không biết nếu có tùy chọn khác nhưng chúng tôi có thể loại bỏ giá trị bằng cách sử dụng:

select oid from pg_type where typname = 'fase';'
select * from pg_enum where enumtypid = 24773;'
select * from pg_enum where enumtypid = 24773 and enumsortorder = 6;
delete from pg_enum where enumtypid = 24773 and enumsortorder = 6;

-2

Khi sử dụng Navicat, bạn có thể chuyển đến các loại (trong chế độ xem -> loại khác -> loại) - nhận chế độ xem thiết kế của loại - và nhấp vào nút "thêm nhãn".


1
Sẽ tốt nhưng trong cuộc sống thực, nó không hữu ích:ERROR: cannot drop type foo because other objects depend on it HINT: Use DROP ... CASCADE to drop the dependent objects too.
Ortwin Gentz

Thật kỳ lạ, nó làm việc cho tôi. (Không chắc chắn lý do tại sao bạn sử dụng DROP khi TS chỉ muốn thêm giá trị vào trường enum)
jvv

1
Tôi đã không làm một DROP cụ thể nhưng đã đi chính xác sau thủ tục của bạn. Tôi cho rằng Navicat thực hiện DROP đằng sau hậu trường và thất bại. Tôi đang sử dụng Navicat 9.1.5 Lite.
Ortwin Gentz
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.