spinta 0.2.dev23__py3-none-any.whl → 0.2.dev25__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spinta/backends/__init__.py +60 -2
- spinta/backends/components.py +8 -1
- spinta/backends/constants.py +10 -0
- spinta/backends/helpers.py +52 -20
- spinta/backends/postgresql/commands/wipe.py +1 -1
- spinta/backends/postgresql/components.py +7 -1
- spinta/backends/postgresql/helpers/migrate/actions.py +82 -3
- spinta/backends/postgresql/helpers/migrate/citus.py +383 -0
- spinta/cli/admin.py +2 -0
- spinta/cli/comment.py +113 -0
- spinta/cli/helpers/admin/components.py +3 -0
- spinta/cli/helpers/admin/registry.py +14 -0
- spinta/cli/helpers/admin/scripts/add_local_ids.py +80 -0
- spinta/cli/helpers/admin/scripts/citus_shard.py +126 -0
- spinta/cli/helpers/admin/scripts/remove_local_ids.py +55 -0
- spinta/cli/helpers/enums.py +5 -0
- spinta/cli/helpers/upgrade/scripts/backends/postgresql/comments.py +62 -26
- spinta/cli/inspect.py +3 -0
- spinta/cli/main.py +4 -0
- spinta/cli/manifest.py +11 -9
- spinta/cli/uncomment.py +110 -0
- spinta/components.py +17 -1
- spinta/config.py +4 -0
- spinta/config.yml +12 -1
- spinta/core/access.py +2 -0
- spinta/datasets/backends/dataframe/commands/read.py +5 -2
- spinta/datasets/backends/dataframe/ufuncs/query/ufuncs.py +11 -1
- spinta/datasets/backends/helpers.py +2 -1
- spinta/datasets/backends/sql/commands/cast.py +20 -17
- spinta/datasets/backends/sql/commands/read.py +50 -15
- spinta/datasets/backends/sql/ufuncs/query/ufuncs.py +11 -1
- spinta/datasets/components.py +0 -1
- spinta/datasets/helpers.py +36 -3
- spinta/dimensions/comments/components.py +3 -0
- spinta/dimensions/comments/helpers.py +2 -0
- spinta/dimensions/scope/__init__.py +0 -0
- spinta/dimensions/scope/components.py +64 -0
- spinta/dimensions/scope/helpers.py +56 -0
- spinta/dimensions/scope/ufuncs.py +51 -0
- spinta/exceptions.py +24 -5
- spinta/formats/html/commands.py +8 -5
- spinta/formats/html/helpers.py +7 -1
- spinta/manifests/commands/link.py +2 -0
- spinta/manifests/internal_sql/helpers.py +4 -2
- spinta/manifests/mermaid/helpers.py +251 -180
- spinta/manifests/sql/helpers.py +1 -1
- spinta/manifests/tabular/components.py +19 -0
- spinta/manifests/tabular/helpers.py +110 -6
- spinta/testing/citus.py +96 -0
- spinta/testing/cli.py +13 -0
- spinta/testing/pytest.py +35 -22
- spinta/types/__init__.py +1 -0
- spinta/types/array/__init__.py +1 -0
- spinta/types/array/link.py +6 -3
- spinta/types/backref/__init__.py +1 -0
- spinta/types/backref/link.py +9 -3
- spinta/types/config.py +13 -1
- spinta/types/datatype.py +11 -0
- spinta/types/helpers.py +67 -2
- spinta/types/model.py +91 -4
- spinta/types/ref/__init__.py +1 -0
- spinta/types/ref/link.py +5 -3
- spinta/urlparams.py +2 -0
- spinta/utils/enums.py +17 -8
- spinta/utils/naming.py +2 -2
- spinta/utils/url.py +3 -0
- {spinta-0.2.dev23.dist-info → spinta-0.2.dev25.dist-info}/METADATA +2 -1
- {spinta-0.2.dev23.dist-info → spinta-0.2.dev25.dist-info}/RECORD +71 -59
- {spinta-0.2.dev23.dist-info → spinta-0.2.dev25.dist-info}/WHEEL +0 -0
- {spinta-0.2.dev23.dist-info → spinta-0.2.dev25.dist-info}/entry_points.txt +0 -0
- {spinta-0.2.dev23.dist-info → spinta-0.2.dev25.dist-info}/licenses/LICENSE +0 -0
spinta/backends/__init__.py
CHANGED
|
@@ -13,6 +13,7 @@ from typing import Iterable
|
|
|
13
13
|
from typing import List
|
|
14
14
|
from typing import Optional
|
|
15
15
|
|
|
16
|
+
from cbor2 import dumps as cbor_dumps
|
|
16
17
|
import dateutil
|
|
17
18
|
import shapely.geometry.base
|
|
18
19
|
from geoalchemy2.elements import WKTElement, WKBElement
|
|
@@ -23,7 +24,7 @@ from spinta import commands
|
|
|
23
24
|
from spinta import exceptions
|
|
24
25
|
from spinta.backends.components import Backend
|
|
25
26
|
from spinta.backends.components import SelectTree
|
|
26
|
-
from spinta.backends.helpers import check_unknown_props, get_select_tree, prepare_response
|
|
27
|
+
from spinta.backends.helpers import check_unknown_props, get_select_tree, prepare_response, is_accessible_by_equals_sign
|
|
27
28
|
from spinta.backends.helpers import flat_select_to_nested
|
|
28
29
|
from spinta.backends.helpers import get_model_reserved_props
|
|
29
30
|
from spinta.backends.helpers import get_select_prop_names
|
|
@@ -58,7 +59,19 @@ from spinta.exceptions import (
|
|
|
58
59
|
from spinta.exceptions import NoItemRevision
|
|
59
60
|
from spinta.formats.components import Format
|
|
60
61
|
from spinta.manifests.components import Manifest
|
|
61
|
-
from spinta.types.datatype import
|
|
62
|
+
from spinta.types.datatype import (
|
|
63
|
+
Array,
|
|
64
|
+
ExternalRef,
|
|
65
|
+
Inherit,
|
|
66
|
+
PageType,
|
|
67
|
+
BackRef,
|
|
68
|
+
ArrayBackRef,
|
|
69
|
+
Integer,
|
|
70
|
+
Boolean,
|
|
71
|
+
Denorm,
|
|
72
|
+
Base32,
|
|
73
|
+
String,
|
|
74
|
+
)
|
|
62
75
|
from spinta.types.datatype import Binary
|
|
63
76
|
from spinta.types.datatype import DataType
|
|
64
77
|
from spinta.types.datatype import Date
|
|
@@ -590,12 +603,31 @@ def is_object_id(context: Context, value: str):
|
|
|
590
603
|
|
|
591
604
|
@is_object_id.register(Context, Backend, Model, str)
|
|
592
605
|
def is_object_id(context: Context, backend: Backend, model: Model, value: str):
|
|
606
|
+
return is_object_id(context, backend, model.properties["_id"].dtype, value)
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
@is_object_id.register(Context, Backend, PrimaryKey, str)
|
|
610
|
+
def is_object_id(context: Context, backend: Backend, dtype: PrimaryKey, value: str):
|
|
593
611
|
try:
|
|
594
612
|
return uuid.UUID(value).version == 4
|
|
595
613
|
except ValueError:
|
|
596
614
|
return False
|
|
597
615
|
|
|
598
616
|
|
|
617
|
+
@is_object_id.register(Context, Backend, DataType, str)
|
|
618
|
+
def is_object_id(context: Context, backend: Backend, dtype: DataType, value: str):
|
|
619
|
+
candidate = value
|
|
620
|
+
if is_accessible_by_equals_sign(dtype.prop, value):
|
|
621
|
+
if not value.startswith("="):
|
|
622
|
+
return False
|
|
623
|
+
candidate = value[1:]
|
|
624
|
+
try:
|
|
625
|
+
dtype.load(candidate)
|
|
626
|
+
except exceptions.InvalidValue:
|
|
627
|
+
return False
|
|
628
|
+
return True
|
|
629
|
+
|
|
630
|
+
|
|
599
631
|
@is_object_id.register(Context, Backend, Model, uuid.UUID)
|
|
600
632
|
def is_object_id(context: Context, backend: Backend, model: Model, value: uuid.UUID):
|
|
601
633
|
return value.version == 4
|
|
@@ -1750,6 +1782,13 @@ def cast_backend_to_python(context: Context, dtype: DataType, backend: Backend,
|
|
|
1750
1782
|
return data
|
|
1751
1783
|
|
|
1752
1784
|
|
|
1785
|
+
@commands.cast_backend_to_python.register(Context, String, Backend, object)
|
|
1786
|
+
def cast_backend_to_python(context: Context, dtype: String, backend: Backend, data: Any, **kwargs) -> Any:
|
|
1787
|
+
if data is None or is_nan(data):
|
|
1788
|
+
return None
|
|
1789
|
+
return str(data)
|
|
1790
|
+
|
|
1791
|
+
|
|
1753
1792
|
@commands.cast_backend_to_python.register(Context, UUID, Backend, object)
|
|
1754
1793
|
def cast_backend_to_python(context: Context, dtype: UUID, backend: Backend, data: Any, **kwargs) -> Any:
|
|
1755
1794
|
if is_nan(data):
|
|
@@ -1891,6 +1930,13 @@ def cast_backend_to_python(context: Context, dtype: Ref, backend: Backend, data:
|
|
|
1891
1930
|
|
|
1892
1931
|
processed_data = {}
|
|
1893
1932
|
for key in data:
|
|
1933
|
+
if key == "_id":
|
|
1934
|
+
# _id reaches this dispatch already in its final form — produced by
|
|
1935
|
+
# handle_ref_key_assignment for external readers, or read directly
|
|
1936
|
+
# from the storage column for internal backends. Re-applying the
|
|
1937
|
+
# referenced model's _id cast double-encodes Base32 ids.
|
|
1938
|
+
processed_data[key] = data[key]
|
|
1939
|
+
continue
|
|
1894
1940
|
prop = commands.resolve_property(dtype.prop.model, f"{dtype.prop.place}.{key}")
|
|
1895
1941
|
if prop is not None:
|
|
1896
1942
|
processed_data[key] = commands.cast_backend_to_python(context, prop, backend, data[key], **kwargs)
|
|
@@ -1939,6 +1985,18 @@ def cast_backend_to_python(context: Context, dtype: Denorm, backend: Backend, da
|
|
|
1939
1985
|
return commands.cast_backend_to_python(context, dtype.rel_prop, backend, data, **kwargs)
|
|
1940
1986
|
|
|
1941
1987
|
|
|
1988
|
+
@commands.cast_backend_to_python.register(Context, Base32, Backend, object)
|
|
1989
|
+
def cast_backend_to_python(context: Context, dtype: Base32, backend: Backend, data: Any, **kwargs) -> Any:
|
|
1990
|
+
if is_nan(data):
|
|
1991
|
+
return None
|
|
1992
|
+
if isinstance(data, (list, tuple)):
|
|
1993
|
+
data = cbor_dumps(list(data))
|
|
1994
|
+
else:
|
|
1995
|
+
data = str(data).encode("utf-8")
|
|
1996
|
+
encoded = base64.b32encode(data)
|
|
1997
|
+
return encoded.rstrip(b"=").decode("utf-8")
|
|
1998
|
+
|
|
1999
|
+
|
|
1942
2000
|
@commands.reload_backend_metadata.register(Context, Manifest, Backend)
|
|
1943
2001
|
def reload_backend_metadata(context, manifest, backend):
|
|
1944
2002
|
pass
|
spinta/backends/components.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import contextlib
|
|
4
|
+
import dataclasses
|
|
4
5
|
from typing import Any, Type
|
|
5
6
|
from typing import Dict
|
|
6
7
|
from typing import Optional
|
|
7
8
|
from typing import Set
|
|
8
9
|
|
|
9
|
-
from spinta.backends.constants import BackendOrigin, BackendFeatures
|
|
10
|
+
from spinta.backends.constants import BackendOrigin, BackendFeatures, DistributionType
|
|
10
11
|
from spinta.core.ufuncs import Env
|
|
11
12
|
from spinta.ufuncs.resultbuilder.components import ResultBuilder
|
|
12
13
|
|
|
@@ -58,3 +59,9 @@ class Backend:
|
|
|
58
59
|
|
|
59
60
|
|
|
60
61
|
SelectTree = Optional[Dict[str, "SelectTree"]]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclasses.dataclass
|
|
65
|
+
class DistributionStrategy:
|
|
66
|
+
distribution_type: DistributionType
|
|
67
|
+
property: str | None = None
|
spinta/backends/constants.py
CHANGED
|
@@ -35,3 +35,13 @@ class BackendFeatures(enum.Enum):
|
|
|
35
35
|
|
|
36
36
|
# Backend supports
|
|
37
37
|
EXPAND = "EXPAND"
|
|
38
|
+
|
|
39
|
+
# Backend supports sharding
|
|
40
|
+
DISTRIBUTE = "DISTRIBUTE"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class DistributionType(enum.Enum):
|
|
44
|
+
SCHEMA = "schema"
|
|
45
|
+
TABLE = "table"
|
|
46
|
+
COPY = "copy"
|
|
47
|
+
UNDISTRIBUTED = "undistributed"
|
spinta/backends/helpers.py
CHANGED
|
@@ -26,7 +26,7 @@ from spinta.components import Model
|
|
|
26
26
|
from spinta.components import Namespace
|
|
27
27
|
from spinta.components import Property
|
|
28
28
|
from spinta.exceptions import BackendUnavailable
|
|
29
|
-
from spinta.types.datatype import DataType, Denorm
|
|
29
|
+
from spinta.types.datatype import DataType, Denorm, String, Base32, PrimaryKey
|
|
30
30
|
from spinta.utils.data import take
|
|
31
31
|
from spinta.backends.constants import TableType, BackendOrigin
|
|
32
32
|
|
|
@@ -344,7 +344,7 @@ def get_ns_reserved_props(action: Action) -> list[str]:
|
|
|
344
344
|
return []
|
|
345
345
|
|
|
346
346
|
|
|
347
|
-
@dataclasses.dataclass
|
|
347
|
+
@dataclasses.dataclass(frozen=True)
|
|
348
348
|
class TableIdentifier:
|
|
349
349
|
"""
|
|
350
350
|
Represents a table identifier across logical (app) and PostgreSQL layers.
|
|
@@ -391,35 +391,42 @@ class TableIdentifier:
|
|
|
391
391
|
table_arg: str | None = dataclasses.field(default=None)
|
|
392
392
|
default_pg_schema: str | None = dataclasses.field(default=None)
|
|
393
393
|
|
|
394
|
-
logical_name: str = dataclasses.field(init=False)
|
|
394
|
+
logical_name: str = dataclasses.field(init=False, compare=False)
|
|
395
395
|
# Name with namespace connected with '/', like it is used with Model class
|
|
396
|
-
logical_qualified_name: str = dataclasses.field(init=False)
|
|
396
|
+
logical_qualified_name: str = dataclasses.field(init=False, compare=False)
|
|
397
397
|
|
|
398
|
-
pg_table_name: str = dataclasses.field(init=False)
|
|
399
|
-
pg_schema_name: str | None = dataclasses.field(init=False)
|
|
398
|
+
pg_table_name: str = dataclasses.field(init=False, compare=False)
|
|
399
|
+
pg_schema_name: str | None = dataclasses.field(init=False, compare=False)
|
|
400
400
|
# Used for hashed schema and table names
|
|
401
|
-
pg_qualified_name: str = dataclasses.field(init=False)
|
|
401
|
+
pg_qualified_name: str = dataclasses.field(init=False, compare=False)
|
|
402
402
|
# Escaped qualified name, used for queries
|
|
403
|
-
pg_escaped_qualified_name: str = dataclasses.field(init=False)
|
|
403
|
+
pg_escaped_qualified_name: str = dataclasses.field(init=False, compare=False)
|
|
404
404
|
|
|
405
405
|
def __post_init__(self):
|
|
406
|
-
|
|
406
|
+
logical_name = self.base_name + self.table_type.value
|
|
407
407
|
if self.table_arg:
|
|
408
|
-
|
|
408
|
+
logical_name += "/" + self.table_arg
|
|
409
409
|
|
|
410
|
-
|
|
410
|
+
logical_qualified_name = f"{self.schema}/{logical_name}" if self.schema else logical_name
|
|
411
411
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
if self.pg_schema_name
|
|
420
|
-
else pg_identifier_preparer.quote(self.pg_table_name)
|
|
412
|
+
pg_table_name = get_pg_name(logical_name)
|
|
413
|
+
pg_schema_name = get_pg_name(self.schema) if self.schema else self.default_pg_schema
|
|
414
|
+
pg_qualified_name = f"{pg_schema_name}.{pg_table_name}" if pg_schema_name else pg_table_name
|
|
415
|
+
pg_escaped_qualified_name = (
|
|
416
|
+
f"{pg_identifier_preparer.quote(pg_schema_name)}.{pg_identifier_preparer.quote(pg_table_name)}"
|
|
417
|
+
if pg_schema_name
|
|
418
|
+
else pg_identifier_preparer.quote(pg_table_name)
|
|
421
419
|
)
|
|
422
420
|
|
|
421
|
+
# This is needed because we want to make this dataclass hashable (frozen=True, does that)
|
|
422
|
+
# But because it becomes immutable, we need to set all the attributes manually (the same way dataclass __init__ does).
|
|
423
|
+
object.__setattr__(self, "logical_name", logical_name)
|
|
424
|
+
object.__setattr__(self, "logical_qualified_name", logical_qualified_name)
|
|
425
|
+
object.__setattr__(self, "pg_table_name", pg_table_name)
|
|
426
|
+
object.__setattr__(self, "pg_schema_name", pg_schema_name)
|
|
427
|
+
object.__setattr__(self, "pg_qualified_name", pg_qualified_name)
|
|
428
|
+
object.__setattr__(self, "pg_escaped_qualified_name", pg_escaped_qualified_name)
|
|
429
|
+
|
|
423
430
|
def change_table_type(self, new_type: TableType, table_arg: str | None = None) -> "TableIdentifier":
|
|
424
431
|
return dataclasses.replace(self, table_type=new_type, table_arg=table_arg)
|
|
425
432
|
|
|
@@ -596,3 +603,28 @@ def extract_table_data_from_logical_name(table_name: str) -> tuple[str | None, T
|
|
|
596
603
|
return data[0], table_type, None
|
|
597
604
|
|
|
598
605
|
return None, None, None
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
def is_custom_id_prop(prop: Property) -> bool:
|
|
609
|
+
return prop.name == "_id" and not isinstance(prop.dtype, PrimaryKey)
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def is_custom_revision_prop(prop: Property) -> bool:
|
|
613
|
+
return prop.name == "_revision" and prop.explicitly_given
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def is_accessible_by_equals_sign(id_prop: Property, value: str | int) -> bool:
|
|
617
|
+
if isinstance(id_prop.dtype, Base32):
|
|
618
|
+
return True
|
|
619
|
+
|
|
620
|
+
if isinstance(id_prop.dtype, String):
|
|
621
|
+
return not check_if_model_primary_key_is_composite(id_prop.model)
|
|
622
|
+
|
|
623
|
+
return False
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def check_if_model_primary_key_is_composite(model: Model) -> bool:
|
|
627
|
+
pkeys_count = len(model.external.pkeys)
|
|
628
|
+
if pkeys_count > 1:
|
|
629
|
+
return True
|
|
630
|
+
return False
|
|
@@ -48,7 +48,7 @@ def wipe(context: Context, model: Model, backend: PostgreSQL):
|
|
|
48
48
|
if changelog_table_identifier.pg_schema_name
|
|
49
49
|
else f'"{seqname}"'
|
|
50
50
|
)
|
|
51
|
-
connection.execute(
|
|
51
|
+
connection.execute(sa.func.setval(seq_escaped_named, 1, False))
|
|
52
52
|
|
|
53
53
|
# Delete data table
|
|
54
54
|
table = backend.get_table(model)
|
|
@@ -26,7 +26,13 @@ class PostgreSQL(Backend):
|
|
|
26
26
|
},
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
features = {
|
|
29
|
+
features = {
|
|
30
|
+
BackendFeatures.FILE_BLOCKS,
|
|
31
|
+
BackendFeatures.WRITE,
|
|
32
|
+
BackendFeatures.EXPAND,
|
|
33
|
+
BackendFeatures.PAGINATION,
|
|
34
|
+
BackendFeatures.DISTRIBUTE,
|
|
35
|
+
}
|
|
30
36
|
|
|
31
37
|
engine: Engine = None
|
|
32
38
|
schema: sa.MetaData = None
|
|
@@ -3,7 +3,7 @@ from sqlalchemy.dialects.postgresql import UUID
|
|
|
3
3
|
from sqlalchemy.dialects import postgresql
|
|
4
4
|
|
|
5
5
|
import sqlalchemy as sa
|
|
6
|
-
from typing import TYPE_CHECKING
|
|
6
|
+
from typing import TYPE_CHECKING, Generator
|
|
7
7
|
|
|
8
8
|
from spinta.backends.helpers import TableIdentifier
|
|
9
9
|
from spinta.backends.postgresql.helpers.name import name_changed
|
|
@@ -91,6 +91,19 @@ class RenameTableMigrationAction(MigrationAction):
|
|
|
91
91
|
)
|
|
92
92
|
|
|
93
93
|
|
|
94
|
+
class SetTableCommentMigrationAction(MigrationAction):
|
|
95
|
+
def __init__(self, table_identifier: TableIdentifier, comment: str) -> None:
|
|
96
|
+
self.table_identifier = table_identifier
|
|
97
|
+
self.comment = comment
|
|
98
|
+
|
|
99
|
+
def execute(self, op: "Operations") -> None:
|
|
100
|
+
op.create_table_comment(
|
|
101
|
+
table_name=self.table_identifier.pg_table_name,
|
|
102
|
+
comment=self.comment,
|
|
103
|
+
schema=self.table_identifier.pg_schema_name,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
94
107
|
class AddColumnMigrationAction(MigrationAction):
|
|
95
108
|
def __init__(self, table_identifier: TableIdentifier, column: sa.Column) -> None:
|
|
96
109
|
self.table_identifier = table_identifier
|
|
@@ -149,6 +162,21 @@ class AlterColumnMigrationAction(MigrationAction):
|
|
|
149
162
|
)
|
|
150
163
|
|
|
151
164
|
|
|
165
|
+
class SetColumnCommentMigrationAction(MigrationAction):
|
|
166
|
+
def __init__(self, table_identifier: TableIdentifier, column: str, comment: str) -> None:
|
|
167
|
+
self.table_identifier = table_identifier
|
|
168
|
+
self.comment = comment
|
|
169
|
+
self.column = column
|
|
170
|
+
|
|
171
|
+
def execute(self, op: "Operations") -> None:
|
|
172
|
+
op.alter_column(
|
|
173
|
+
table_name=self.table_identifier.pg_table_name,
|
|
174
|
+
column_name=self.column,
|
|
175
|
+
comment=self.comment,
|
|
176
|
+
schema=self.table_identifier.pg_schema_name,
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
152
180
|
class DropConstraintMigrationAction(MigrationAction):
|
|
153
181
|
def __init__(self, table_identifier: TableIdentifier, constraint_name: str) -> None:
|
|
154
182
|
self.table_identifier = table_identifier
|
|
@@ -547,6 +575,50 @@ class CreateSchemaMigrationAction(MigrationAction):
|
|
|
547
575
|
op.execute(self.query)
|
|
548
576
|
|
|
549
577
|
|
|
578
|
+
class DistributeSchema(MigrationAction):
|
|
579
|
+
def __init__(self, schema_name: str) -> None:
|
|
580
|
+
self.schema_name = schema_name
|
|
581
|
+
self.query = f"SELECT citus_schema_distribute('{pg_identifier_preparer.quote(schema_name)}')"
|
|
582
|
+
|
|
583
|
+
def execute(self, op: "Operations") -> None:
|
|
584
|
+
op.execute(self.query)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
class DistributeReference(MigrationAction):
|
|
588
|
+
def __init__(self, table_identifier: TableIdentifier) -> None:
|
|
589
|
+
self.query = f"SELECT create_reference_table('{table_identifier.pg_escaped_qualified_name}')"
|
|
590
|
+
|
|
591
|
+
def execute(self, op: "Operations") -> None:
|
|
592
|
+
op.execute(self.query)
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
class DistributeTable(MigrationAction):
|
|
596
|
+
def __init__(self, table_identifier: TableIdentifier, column: str) -> None:
|
|
597
|
+
self.query = f"SELECT create_distributed_table('{table_identifier.pg_escaped_qualified_name}', '{column}')"
|
|
598
|
+
|
|
599
|
+
def execute(self, op: "Operations") -> None:
|
|
600
|
+
op.execute(self.query)
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
class UndistributeSchema(MigrationAction):
|
|
604
|
+
def __init__(self, schema_name: str) -> None:
|
|
605
|
+
self.schema_name = schema_name
|
|
606
|
+
self.query = f"SELECT citus_schema_undistribute('{pg_identifier_preparer.quote(schema_name)}')"
|
|
607
|
+
|
|
608
|
+
def execute(self, op: "Operations") -> None:
|
|
609
|
+
op.execute(self.query)
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
class UndistributeTable(MigrationAction):
|
|
613
|
+
def __init__(self, table_identifier: TableIdentifier) -> None:
|
|
614
|
+
self.query = (
|
|
615
|
+
f"SELECT undistribute_table('{table_identifier.pg_escaped_qualified_name}', cascade_via_foreign_keys=>true)"
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
def execute(self, op: "Operations") -> None:
|
|
619
|
+
op.execute(self.query)
|
|
620
|
+
|
|
621
|
+
|
|
550
622
|
class MigrationHandler:
|
|
551
623
|
def __init__(self) -> None:
|
|
552
624
|
self.migrations: list[MigrationAction] = []
|
|
@@ -571,8 +643,15 @@ class MigrationHandler:
|
|
|
571
643
|
return True
|
|
572
644
|
return False
|
|
573
645
|
|
|
574
|
-
def
|
|
646
|
+
def gather_migrations(self) -> Generator[MigrationAction, None, None]:
|
|
575
647
|
for migration in self.migrations:
|
|
576
|
-
migration
|
|
648
|
+
yield migration
|
|
577
649
|
for migration in self.foreign_key_migration:
|
|
650
|
+
yield migration
|
|
651
|
+
|
|
652
|
+
def run_migrations(self, op: "Operations") -> None:
|
|
653
|
+
for migration in self.gather_migrations():
|
|
578
654
|
migration.execute(op)
|
|
655
|
+
|
|
656
|
+
def count(self) -> int:
|
|
657
|
+
return len(list(self.gather_migrations()))
|