1import operator
2
3from plain.models.backends.base.features import BaseDatabaseFeatures
4from plain.models.backends.postgresql.psycopg_any import is_psycopg3
5from plain.models.db import DataError, InterfaceError
6from plain.utils.functional import cached_property
7
8
9class DatabaseFeatures(BaseDatabaseFeatures):
10 minimum_database_version = (12,)
11 allows_group_by_selected_pks = True
12 can_return_columns_from_insert = True
13 can_return_rows_from_bulk_insert = True
14 has_real_datatype = True
15 has_native_uuid_field = True
16 has_native_duration_field = True
17 has_native_json_field = True
18 can_defer_constraint_checks = True
19 has_select_for_update = True
20 has_select_for_update_nowait = True
21 has_select_for_update_of = True
22 has_select_for_update_skip_locked = True
23 has_select_for_no_key_update = True
24 can_release_savepoints = True
25 supports_comments = True
26 supports_tablespaces = True
27 supports_transactions = True
28 can_introspect_materialized_views = True
29 can_distinct_on_fields = True
30 can_rollback_ddl = True
31 schema_editor_uses_clientside_param_binding = True
32 supports_combined_alters = True
33 nulls_order_largest = True
34 closed_cursor_error_class = InterfaceError
35 greatest_least_ignores_nulls = True
36 can_clone_databases = True
37 supports_temporal_subtraction = True
38 supports_slicing_ordering_in_compound = True
39 create_test_procedure_without_params_sql = """
40 CREATE FUNCTION test_procedure () RETURNS void AS $$
41 DECLARE
42 V_I INTEGER;
43 BEGIN
44 V_I := 1;
45 END;
46 $$ LANGUAGE plpgsql;"""
47 create_test_procedure_with_int_param_sql = """
48 CREATE FUNCTION test_procedure (P_I INTEGER) RETURNS void AS $$
49 DECLARE
50 V_I INTEGER;
51 BEGIN
52 V_I := P_I;
53 END;
54 $$ LANGUAGE plpgsql;"""
55 create_test_table_with_composite_primary_key = """
56 CREATE TABLE test_table_composite_pk (
57 column_1 INTEGER NOT NULL,
58 column_2 INTEGER NOT NULL,
59 PRIMARY KEY(column_1, column_2)
60 )
61 """
62 requires_casted_case_in_updates = True
63 supports_over_clause = True
64 only_supports_unbounded_with_preceding_and_following = True
65 supports_aggregate_filter_clause = True
66 supported_explain_formats = {"JSON", "TEXT", "XML", "YAML"}
67 supports_deferrable_unique_constraints = True
68 has_json_operators = True
69 json_key_contains_list_matching_requires_list = True
70 supports_update_conflicts = True
71 supports_update_conflicts_with_target = True
72 supports_covering_indexes = True
73 can_rename_index = True
74 test_collations = {
75 "non_default": "sv-x-icu",
76 "swedish_ci": "sv-x-icu",
77 }
78 test_now_utc_template = "STATEMENT_TIMESTAMP() AT TIME ZONE 'UTC'"
79
80 @cached_property
81 def uses_server_side_binding(self):
82 options = self.connection.settings_dict["OPTIONS"]
83 return is_psycopg3 and options.get("server_side_binding") is True
84
85 @cached_property
86 def prohibits_null_characters_in_text_exception(self):
87 if is_psycopg3:
88 return DataError, "PostgreSQL text fields cannot contain NUL (0x00) bytes"
89 else:
90 return ValueError, "A string literal cannot contain NUL (0x00) characters."
91
92 @cached_property
93 def introspected_field_types(self):
94 return {
95 **super().introspected_field_types,
96 "PositiveBigIntegerField": "BigIntegerField",
97 "PositiveIntegerField": "IntegerField",
98 "PositiveSmallIntegerField": "SmallIntegerField",
99 }
100
101 @cached_property
102 def is_postgresql_13(self):
103 return self.connection.pg_version >= 130000
104
105 @cached_property
106 def is_postgresql_14(self):
107 return self.connection.pg_version >= 140000
108
109 has_bit_xor = property(operator.attrgetter("is_postgresql_14"))
110 supports_covering_spgist_indexes = property(operator.attrgetter("is_postgresql_14"))
111 supports_unlimited_charfield = True