DefPixel-PostgreSQL 0.1.5__83-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.
- DefPixel/DataAccessObject/Implements/PostgreSQL/Adapters/Array.py +16 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Adapters/JavaScriptObjectNotation.py +74 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Adapters/__init__.py +51 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Column.py +34 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Compiler.py +161 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Configuration.py +116 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Connection.py +138 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Constraint.py +18 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Constraints/Check.py +23 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Constraints/Columns.py +24 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Constraints/ForeignKey.py +56 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Constraints/PrimaryKey.py +21 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Constraints/Unique.py +25 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Constraints/__init__.py +13 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Context.py +48 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Cursor.py +123 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Executors/Create.py +181 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Executors/Delete.py +31 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Executors/Drop.py +53 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Executors/Get.py +24 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Executors/Insert.py +98 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Executors/Query.py +37 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Executors/Select.py +122 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Executors/Update.py +57 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Executors/__init__.py +19 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Expr.py +11 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Exprs/Common.py +180 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Exprs/Compare.py +87 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Exprs/Condition.py +155 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Exprs/JavaScriptObjectNotation.py +53 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Exprs/Operator.py +42 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Exprs/String.py +48 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Exprs/__init__.py +75 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Function.py +11 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Functions/Common.py +142 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Functions/DateTime.py +219 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Functions/JavaScriptObjectNotation.py +100 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Functions/Sequence.py +55 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Functions/__init__.py +91 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Index.py +135 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Joins/FullOuter.py +15 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Joins/Inner.py +15 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Joins/Join.py +41 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Joins/LeftOuter.py +15 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Joins/RightOuter.py +15 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Joins/__init__.py +15 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Mapping.py +359 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Orders.py +59 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Pool.py +84 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Schema.py +28 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Sequence.py +55 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Session.py +84 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Type.py +113 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Types/Array.py +61 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Types/Binary.py +34 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Types/Bool.py +29 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Types/DateTime.py +120 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Types/JavaScriptObjectNotation.py +52 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Types/Numeric.py +151 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Types/Size.py +13 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Types/String.py +80 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/Types/__init__.py +76 -0
- DefPixel/DataAccessObject/Implements/PostgreSQL/__init__.py +85 -0
- defpixel_postgresql-0.1.5.dist-info/METADATA +180 -0
- defpixel_postgresql-0.1.5.dist-info/RECORD +68 -0
- defpixel_postgresql-0.1.5.dist-info/WHEEL +6 -0
- defpixel_postgresql-0.1.5.dist-info/licenses/LICENSE +674 -0
- defpixel_postgresql-0.1.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from ..Compiler import identifier
|
|
6
|
+
from ..Constraint import Constraint
|
|
7
|
+
from .Columns import columns
|
|
8
|
+
|
|
9
|
+
__all__ = (
|
|
10
|
+
'ForeignKey',
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
ACTIONS = (
|
|
14
|
+
'NO ACTION',
|
|
15
|
+
'RESTRICT',
|
|
16
|
+
'CASCADE',
|
|
17
|
+
'SET NULL',
|
|
18
|
+
'SET DEFAULT',
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def action(name: str, value: Optional[str]) -> Optional[str]:
|
|
23
|
+
if value is None:
|
|
24
|
+
return None
|
|
25
|
+
if not isinstance(value, str) or ' '.join(value.upper().split()) not in ACTIONS:
|
|
26
|
+
raise ValueError('{} must be one of: {}'.format(name, ', '.join(ACTIONS)))
|
|
27
|
+
return ' '.join(value.upper().split())
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ForeignKey(Constraint):
|
|
31
|
+
"""Named foreign key; reference is a mapped model or a table name."""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
name: str,
|
|
36
|
+
cols,
|
|
37
|
+
reference=None,
|
|
38
|
+
referenceCols=None,
|
|
39
|
+
onDelete: Optional[str] = None,
|
|
40
|
+
onUpdate: Optional[str] = None,
|
|
41
|
+
):
|
|
42
|
+
super().__init__(name)
|
|
43
|
+
if reference is None:
|
|
44
|
+
raise ValueError('ForeignKey requires a reference')
|
|
45
|
+
if isinstance(reference, str):
|
|
46
|
+
identifier(reference)
|
|
47
|
+
elif not isinstance(reference, type):
|
|
48
|
+
raise TypeError('ForeignKey reference must be a mapped model or a table name')
|
|
49
|
+
self.cols = columns(cols)
|
|
50
|
+
self.reference = reference
|
|
51
|
+
self.referenceCols = columns(referenceCols, 'referenceCols')
|
|
52
|
+
if len(self.cols) != len(self.referenceCols):
|
|
53
|
+
raise ValueError('Foreign key column counts do not match')
|
|
54
|
+
self.onDelete = action('onDelete', onDelete)
|
|
55
|
+
self.onUpdate = action('onUpdate', onUpdate)
|
|
56
|
+
return
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from ..Constraint import Constraint
|
|
4
|
+
from .Columns import columns
|
|
5
|
+
|
|
6
|
+
__all__ = (
|
|
7
|
+
'PrimaryKey',
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PrimaryKey(Constraint):
|
|
12
|
+
"""Named primary key constraint."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
name: str,
|
|
17
|
+
cols,
|
|
18
|
+
):
|
|
19
|
+
super().__init__(name)
|
|
20
|
+
self.cols = columns(cols)
|
|
21
|
+
return
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from ..Constraint import Constraint
|
|
4
|
+
from .Columns import columns
|
|
5
|
+
|
|
6
|
+
__all__ = (
|
|
7
|
+
'Unique',
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Unique(Constraint):
|
|
12
|
+
"""Named unique constraint; null=True treats NULL values as equal (NULLS NOT DISTINCT)."""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
name: str,
|
|
17
|
+
cols,
|
|
18
|
+
null: bool = False,
|
|
19
|
+
):
|
|
20
|
+
super().__init__(name)
|
|
21
|
+
if not isinstance(null, bool):
|
|
22
|
+
raise TypeError('null must be a bool')
|
|
23
|
+
self.cols = columns(cols)
|
|
24
|
+
self.null = null
|
|
25
|
+
return
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from typing import (
|
|
4
|
+
Any,
|
|
5
|
+
Dict,
|
|
6
|
+
List,
|
|
7
|
+
Optional,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from DefPixel.DataAccessObject.Properties.Database import Context as BaseContext
|
|
11
|
+
|
|
12
|
+
__all__ = (
|
|
13
|
+
'Context',
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Context(BaseContext):
|
|
18
|
+
"""Result of a PostgreSQL execution; reading rows advances the cursor."""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
cursor,
|
|
23
|
+
query: Optional[str] = None,
|
|
24
|
+
):
|
|
25
|
+
self.cursor = cursor
|
|
26
|
+
self.statement = query
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
def rows(self) -> List[Dict[str, Any]]:
|
|
30
|
+
if self.cursor.description is None:
|
|
31
|
+
return []
|
|
32
|
+
return [dict(row) for row in self.cursor.fetchall()]
|
|
33
|
+
|
|
34
|
+
def row(self) -> Optional[Dict[str, Any]]:
|
|
35
|
+
if self.cursor.description is None:
|
|
36
|
+
return None
|
|
37
|
+
row = self.cursor.fetchone()
|
|
38
|
+
return dict(row) if row is not None else None
|
|
39
|
+
|
|
40
|
+
def count(self) -> int:
|
|
41
|
+
"""Return the driver rowcount: affected or returned rows, or -1 when not applicable."""
|
|
42
|
+
return self.cursor.rowcount
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def query(self) -> str:
|
|
46
|
+
if self.statement is None:
|
|
47
|
+
raise RuntimeError('The executed query is not available in this context')
|
|
48
|
+
return self.statement
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from collections.abc import (
|
|
4
|
+
Mapping,
|
|
5
|
+
Sequence,
|
|
6
|
+
)
|
|
7
|
+
from typing import (
|
|
8
|
+
Any,
|
|
9
|
+
Dict,
|
|
10
|
+
List,
|
|
11
|
+
Optional,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from DefPixel.DataAccessObject.Properties.Database import (
|
|
15
|
+
Cursor as BaseCursor,
|
|
16
|
+
Executor,
|
|
17
|
+
Executors,
|
|
18
|
+
Fetch,
|
|
19
|
+
Run,
|
|
20
|
+
)
|
|
21
|
+
from .Context import Context
|
|
22
|
+
from .Mapping import inspect_model
|
|
23
|
+
|
|
24
|
+
__all__ = (
|
|
25
|
+
'Cursor',
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def parameters(
|
|
30
|
+
args: tuple,
|
|
31
|
+
kwargs: dict,
|
|
32
|
+
):
|
|
33
|
+
"""Return positional or named parameters; never both."""
|
|
34
|
+
if args and kwargs:
|
|
35
|
+
raise ValueError('Positional and named parameters cannot be used together')
|
|
36
|
+
if kwargs:
|
|
37
|
+
return kwargs
|
|
38
|
+
if args:
|
|
39
|
+
return args
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def kind(params: Any) -> str:
|
|
44
|
+
if isinstance(params, Mapping):
|
|
45
|
+
return 'named'
|
|
46
|
+
if isinstance(params, Sequence) and not isinstance(params, (str, bytes, bytearray)):
|
|
47
|
+
return 'positional'
|
|
48
|
+
raise TypeError('Each parameter set must be a sequence or a mapping')
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Cursor(BaseCursor, Run):
|
|
52
|
+
"""PostgreSQL cursor supporting direct SQL and ORM execution."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, cursor):
|
|
55
|
+
self.cursor = cursor
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
def execute(
|
|
59
|
+
self,
|
|
60
|
+
sql: str,
|
|
61
|
+
*args,
|
|
62
|
+
**kwargs,
|
|
63
|
+
) -> Context:
|
|
64
|
+
self.cursor.execute(sql, parameters(args, kwargs))
|
|
65
|
+
return Context(self.cursor, sql)
|
|
66
|
+
|
|
67
|
+
def executes(
|
|
68
|
+
self,
|
|
69
|
+
sql: str,
|
|
70
|
+
*args,
|
|
71
|
+
) -> Context:
|
|
72
|
+
kinds = {kind(params) for params in args}
|
|
73
|
+
if len(kinds) > 1:
|
|
74
|
+
raise ValueError('Positional and named parameter sets cannot be mixed')
|
|
75
|
+
self.cursor.executemany(sql, args)
|
|
76
|
+
return Context(self.cursor, sql)
|
|
77
|
+
|
|
78
|
+
def run(
|
|
79
|
+
self,
|
|
80
|
+
executor,
|
|
81
|
+
filter=None,
|
|
82
|
+
fetch=None,
|
|
83
|
+
):
|
|
84
|
+
if fetch is not None:
|
|
85
|
+
inspect_model(fetch)
|
|
86
|
+
if isinstance(executor, Executor):
|
|
87
|
+
self.cursor.execute(executor.query, executor.args)
|
|
88
|
+
if isinstance(executor, Fetch):
|
|
89
|
+
return executor.fetch(
|
|
90
|
+
self,
|
|
91
|
+
filter=filter,
|
|
92
|
+
fetch=fetch,
|
|
93
|
+
)
|
|
94
|
+
return None
|
|
95
|
+
if isinstance(executor, Executors):
|
|
96
|
+
result = []
|
|
97
|
+
for statement in executor:
|
|
98
|
+
if not isinstance(statement, Executor):
|
|
99
|
+
raise TypeError('Executors must yield Executor objects')
|
|
100
|
+
value = self.run(
|
|
101
|
+
statement,
|
|
102
|
+
filter=filter,
|
|
103
|
+
fetch=fetch,
|
|
104
|
+
)
|
|
105
|
+
if isinstance(value, list):
|
|
106
|
+
result.extend(value)
|
|
107
|
+
elif value is not None:
|
|
108
|
+
result.append(value)
|
|
109
|
+
return result
|
|
110
|
+
raise TypeError('run requires an Executor or Executors object')
|
|
111
|
+
|
|
112
|
+
def rows(self) -> List[Dict[str, Any]]:
|
|
113
|
+
return Context(self.cursor).rows()
|
|
114
|
+
|
|
115
|
+
def row(self) -> Optional[Dict[str, Any]]:
|
|
116
|
+
return Context(self.cursor).row()
|
|
117
|
+
|
|
118
|
+
def count(self) -> int:
|
|
119
|
+
return self.cursor.rowcount
|
|
120
|
+
|
|
121
|
+
def close(self):
|
|
122
|
+
self.cursor.close()
|
|
123
|
+
return
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from DefPixel.DataAccessObject.Properties.Database import Executors
|
|
6
|
+
|
|
7
|
+
from ..Column import Column
|
|
8
|
+
from ..Compiler import (
|
|
9
|
+
Compilable,
|
|
10
|
+
Compiler,
|
|
11
|
+
identifier,
|
|
12
|
+
qualified,
|
|
13
|
+
typename,
|
|
14
|
+
)
|
|
15
|
+
from ..Constraints import (
|
|
16
|
+
Check,
|
|
17
|
+
ForeignKey,
|
|
18
|
+
PrimaryKey,
|
|
19
|
+
Unique,
|
|
20
|
+
)
|
|
21
|
+
from ..Index import (
|
|
22
|
+
Index,
|
|
23
|
+
IndexOperation,
|
|
24
|
+
)
|
|
25
|
+
from ..Mapping import (
|
|
26
|
+
Mapping,
|
|
27
|
+
Statement,
|
|
28
|
+
inspect_model,
|
|
29
|
+
)
|
|
30
|
+
from ..Schema import Schema
|
|
31
|
+
from ..Sequence import Sequence
|
|
32
|
+
from ..Type import Type
|
|
33
|
+
|
|
34
|
+
__all__ = (
|
|
35
|
+
'Create',
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def ddl() -> Compiler:
|
|
40
|
+
return Compiler(literal=True, qualify=False)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def reference(mapping, col) -> str:
|
|
44
|
+
if isinstance(col, Type):
|
|
45
|
+
return identifier(col.key)
|
|
46
|
+
if isinstance(col, Column):
|
|
47
|
+
return col.compile(ddl())
|
|
48
|
+
if mapping is not None:
|
|
49
|
+
return identifier(mapping.key(col))
|
|
50
|
+
return identifier(col)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def column(spec: Type) -> str:
|
|
54
|
+
default = spec.default()
|
|
55
|
+
return '{} {}{}{}'.format(
|
|
56
|
+
identifier(spec.key),
|
|
57
|
+
typename(spec),
|
|
58
|
+
' NOT NULL' if not spec.null else '',
|
|
59
|
+
' DEFAULT {}'.format(default) if default is not None else '',
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def constraint(mapping: Mapping, item) -> str:
|
|
64
|
+
name = identifier(item.name)
|
|
65
|
+
if isinstance(item, PrimaryKey):
|
|
66
|
+
return 'CONSTRAINT {} PRIMARY KEY({})'.format(name, ', '.join(reference(mapping, col) for col in item.cols))
|
|
67
|
+
if isinstance(item, Unique):
|
|
68
|
+
return 'CONSTRAINT {} UNIQUE{}({})'.format(
|
|
69
|
+
name,
|
|
70
|
+
' NULLS NOT DISTINCT' if item.null else '',
|
|
71
|
+
', '.join(reference(mapping, col) for col in item.cols),
|
|
72
|
+
)
|
|
73
|
+
if isinstance(item, Check):
|
|
74
|
+
return 'CONSTRAINT {} CHECK({})'.format(name, ddl().compile(item.expr))
|
|
75
|
+
if isinstance(item, ForeignKey):
|
|
76
|
+
if isinstance(item.reference, str):
|
|
77
|
+
target, other = identifier(item.reference), None
|
|
78
|
+
else:
|
|
79
|
+
other = inspect_model(item.reference, 'table')
|
|
80
|
+
target = other.target
|
|
81
|
+
return 'CONSTRAINT {} FOREIGN KEY({}) REFERENCES {}({}){}{}'.format(
|
|
82
|
+
name,
|
|
83
|
+
', '.join(reference(mapping, col) for col in item.cols),
|
|
84
|
+
target,
|
|
85
|
+
', '.join(reference(other, col) for col in item.referenceCols),
|
|
86
|
+
' ON UPDATE {}'.format(item.onUpdate) if item.onUpdate else '',
|
|
87
|
+
' ON DELETE {}'.format(item.onDelete) if item.onDelete else '',
|
|
88
|
+
)
|
|
89
|
+
raise TypeError('Unsupported constraint: {}'.format(type(item).__name__))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def sequence(item: Sequence) -> str:
|
|
93
|
+
return 'CREATE SEQUENCE {}{} AS {} INCREMENT BY {} {} {}{}'.format(
|
|
94
|
+
'IF NOT EXISTS ' if item.notexists else '',
|
|
95
|
+
qualified(item.name, item.schema),
|
|
96
|
+
typename(item.type),
|
|
97
|
+
item.increment,
|
|
98
|
+
'NO MINVALUE' if item.min is None else 'MINVALUE {}'.format(item.min),
|
|
99
|
+
'NO MAXVALUE' if item.max is None else 'MAXVALUE {}'.format(item.max),
|
|
100
|
+
'' if item.start is None else ' START WITH {}'.format(item.start),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def extension(item: Index) -> str:
|
|
105
|
+
return (
|
|
106
|
+
"DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = '{0}') THEN "
|
|
107
|
+
"RAISE EXCEPTION 'The {0} extension is required for index {1}; run CREATE EXTENSION {0} first'; "
|
|
108
|
+
"END IF; END $$"
|
|
109
|
+
).format(item.extension, item.name.replace("'", "''").replace('%', '%%').replace('$', ''))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def index(mapping: Mapping, item: Index) -> str:
|
|
113
|
+
compiler = ddl()
|
|
114
|
+
exprs = []
|
|
115
|
+
for expr in item.exprs:
|
|
116
|
+
if isinstance(expr, str):
|
|
117
|
+
exprs.append(identifier(mapping.key(expr) if expr in mapping.columns else expr))
|
|
118
|
+
elif (
|
|
119
|
+
isinstance(expr, (Type, IndexOperation))
|
|
120
|
+
or isinstance(expr, Column) and expr.typecast is None
|
|
121
|
+
):
|
|
122
|
+
exprs.append(compiler.compile(expr))
|
|
123
|
+
else:
|
|
124
|
+
exprs.append('({})'.format(compiler.compile(expr)))
|
|
125
|
+
return 'CREATE {}INDEX {}{} ON {}{}({}){}'.format(
|
|
126
|
+
'UNIQUE ' if item.unique else '',
|
|
127
|
+
'IF NOT EXISTS ' if item.notexists else '',
|
|
128
|
+
identifier(item.name),
|
|
129
|
+
mapping.target,
|
|
130
|
+
' USING {}'.format(item.using.value) if item.using else '',
|
|
131
|
+
', '.join(exprs),
|
|
132
|
+
' WHERE {}'.format(' AND '.join(compiler.compile(cond) for cond in item.conds)) if item.conds else '',
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class Create(Executors):
|
|
137
|
+
"""DDL statements that create a mapped table or view, or a Schema."""
|
|
138
|
+
|
|
139
|
+
def __init__(
|
|
140
|
+
self,
|
|
141
|
+
o,
|
|
142
|
+
notexist: bool = True,
|
|
143
|
+
):
|
|
144
|
+
self.o = o
|
|
145
|
+
self.notexist = bool(notexist)
|
|
146
|
+
self.executors = self.statements()
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
def statements(self) -> List[Statement]:
|
|
150
|
+
if isinstance(self.o, Schema):
|
|
151
|
+
return [Statement('CREATE SCHEMA {}{}{}'.format(
|
|
152
|
+
'IF NOT EXISTS ' if self.notexist else '',
|
|
153
|
+
identifier(self.o.name),
|
|
154
|
+
' AUTHORIZATION {}'.format(identifier(self.o.username)) if self.o.username else '',
|
|
155
|
+
))]
|
|
156
|
+
mapping = inspect_model(self.o)
|
|
157
|
+
if mapping.kind == 'view':
|
|
158
|
+
executor = mapping.executor
|
|
159
|
+
if not isinstance(executor, Compilable):
|
|
160
|
+
raise TypeError('View definitions require a PostgreSQL query Executor')
|
|
161
|
+
return [Statement('CREATE {}VIEW {} AS {}'.format(
|
|
162
|
+
'OR REPLACE ' if self.notexist else '',
|
|
163
|
+
mapping.target,
|
|
164
|
+
executor.compile(Compiler(literal=True)),
|
|
165
|
+
))]
|
|
166
|
+
statements = [Statement(sequence(item)) for item in mapping.sequences]
|
|
167
|
+
definitions = [column(spec) for spec in mapping.columns.values()]
|
|
168
|
+
definitions.extend(constraint(mapping, item) for item in mapping.constraints)
|
|
169
|
+
statements.append(Statement('CREATE TABLE {}{}({})'.format(
|
|
170
|
+
'IF NOT EXISTS ' if self.notexist else '',
|
|
171
|
+
mapping.target,
|
|
172
|
+
', '.join(definitions),
|
|
173
|
+
)))
|
|
174
|
+
for item in mapping.indexes:
|
|
175
|
+
if item.extension:
|
|
176
|
+
statements.append(Statement(extension(item)))
|
|
177
|
+
statements.append(Statement(index(mapping, item)))
|
|
178
|
+
return statements
|
|
179
|
+
|
|
180
|
+
def __iter__(self):
|
|
181
|
+
return iter(self.executors)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from DefPixel.DataAccessObject.Properties.Database import Executor
|
|
4
|
+
|
|
5
|
+
from ..Compiler import Compiler
|
|
6
|
+
from ..Exprs.Operator import conditions
|
|
7
|
+
from ..Mapping import inspect_model
|
|
8
|
+
from .Query import Query
|
|
9
|
+
|
|
10
|
+
__all__ = (
|
|
11
|
+
'Delete',
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Delete(Query, Executor):
|
|
16
|
+
"""DELETE FROM ... [WHERE ...]."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, o):
|
|
19
|
+
self.mapping = inspect_model(o, 'table')
|
|
20
|
+
self.conds = ()
|
|
21
|
+
return
|
|
22
|
+
|
|
23
|
+
def where(self, *args):
|
|
24
|
+
self.conds = conditions(args)
|
|
25
|
+
return self
|
|
26
|
+
|
|
27
|
+
def compile(self, compiler: Compiler) -> str:
|
|
28
|
+
return 'DELETE FROM {}{}'.format(
|
|
29
|
+
self.mapping.target,
|
|
30
|
+
' WHERE {}'.format(' AND '.join(compiler.compile(cond) for cond in self.conds)) if self.conds else '',
|
|
31
|
+
)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from DefPixel.DataAccessObject.Properties.Database import Executors
|
|
6
|
+
|
|
7
|
+
from ..Compiler import (
|
|
8
|
+
identifier,
|
|
9
|
+
qualified,
|
|
10
|
+
)
|
|
11
|
+
from ..Mapping import (
|
|
12
|
+
Statement,
|
|
13
|
+
inspect_model,
|
|
14
|
+
)
|
|
15
|
+
from ..Schema import Schema
|
|
16
|
+
|
|
17
|
+
__all__ = (
|
|
18
|
+
'Drop',
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Drop(Executors):
|
|
23
|
+
"""DDL statements that drop a mapped table or view, or a Schema."""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
o,
|
|
28
|
+
exist: bool = True,
|
|
29
|
+
cascade: bool = False,
|
|
30
|
+
):
|
|
31
|
+
self.o = o
|
|
32
|
+
self.exist = bool(exist)
|
|
33
|
+
self.cascade = bool(cascade)
|
|
34
|
+
self.executors = self.statements()
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
def statements(self) -> List[Statement]:
|
|
38
|
+
exists = 'IF EXISTS ' if self.exist else ''
|
|
39
|
+
mode = ' CASCADE' if self.cascade else ' RESTRICT'
|
|
40
|
+
if isinstance(self.o, Schema):
|
|
41
|
+
return [Statement('DROP SCHEMA {}{}{}'.format(exists, identifier(self.o.name), mode))]
|
|
42
|
+
mapping = inspect_model(self.o)
|
|
43
|
+
if mapping.kind == 'view':
|
|
44
|
+
return [Statement('DROP VIEW {}{}{}'.format(exists, mapping.target, mode))]
|
|
45
|
+
statements = [Statement('DROP TABLE {}{}{}'.format(exists, mapping.target, mode))]
|
|
46
|
+
statements.extend(
|
|
47
|
+
Statement('DROP SEQUENCE {}{}{}'.format(exists, qualified(item.name, item.schema), mode))
|
|
48
|
+
for item in mapping.sequences
|
|
49
|
+
)
|
|
50
|
+
return statements
|
|
51
|
+
|
|
52
|
+
def __iter__(self):
|
|
53
|
+
return iter(self.executors)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from .Select import Select
|
|
4
|
+
|
|
5
|
+
__all__ = (
|
|
6
|
+
'Get',
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Get(Select):
|
|
11
|
+
"""Select that returns the first result or None."""
|
|
12
|
+
|
|
13
|
+
def fetch(
|
|
14
|
+
self,
|
|
15
|
+
cursor,
|
|
16
|
+
filter=None,
|
|
17
|
+
fetch=None,
|
|
18
|
+
):
|
|
19
|
+
rows = super().fetch(
|
|
20
|
+
cursor,
|
|
21
|
+
filter=filter,
|
|
22
|
+
fetch=fetch,
|
|
23
|
+
)
|
|
24
|
+
return rows[0] if rows else None
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
from DefPixel.DataAccessObject.Properties.Database import (
|
|
4
|
+
Executor,
|
|
5
|
+
Fetch,
|
|
6
|
+
)
|
|
7
|
+
from ..Column import Column
|
|
8
|
+
from ..Compiler import (
|
|
9
|
+
Compiler,
|
|
10
|
+
identifier,
|
|
11
|
+
valuetype,
|
|
12
|
+
)
|
|
13
|
+
from ..Mapping import inspect_model
|
|
14
|
+
from ..Type import Type
|
|
15
|
+
from .Query import Query
|
|
16
|
+
|
|
17
|
+
__all__ = (
|
|
18
|
+
'Insert',
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def first(
|
|
23
|
+
cursor,
|
|
24
|
+
filter=None,
|
|
25
|
+
fetch=None,
|
|
26
|
+
):
|
|
27
|
+
rows = cursor.rows()
|
|
28
|
+
if not rows:
|
|
29
|
+
return None
|
|
30
|
+
row = rows[0]
|
|
31
|
+
if filter is not None:
|
|
32
|
+
row = filter(row)
|
|
33
|
+
if fetch is not None:
|
|
34
|
+
return inspect_model(fetch).restore(row)
|
|
35
|
+
return row
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Insert(Query, Executor, Fetch):
|
|
39
|
+
"""INSERT ... RETURNING * with optional ON CONFLICT DO NOTHING or DO UPDATE SET."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, o):
|
|
42
|
+
self.mapping = inspect_model(o, 'table')
|
|
43
|
+
self.kwargs = {}
|
|
44
|
+
self.ons = ()
|
|
45
|
+
self.onkwargs = {}
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
def values(self, **kwargs):
|
|
49
|
+
self.kwargs.update(self.mapping.values(kwargs))
|
|
50
|
+
return self
|
|
51
|
+
|
|
52
|
+
def on(self, *args):
|
|
53
|
+
ons = []
|
|
54
|
+
for arg in args:
|
|
55
|
+
if isinstance(arg, Type):
|
|
56
|
+
ons.append(arg.key)
|
|
57
|
+
elif isinstance(arg, Column):
|
|
58
|
+
ons.append(arg.name)
|
|
59
|
+
elif isinstance(arg, str):
|
|
60
|
+
ons.append(self.mapping.key(arg))
|
|
61
|
+
else:
|
|
62
|
+
raise TypeError('Conflict targets must be column names, Column objects, or column specifications')
|
|
63
|
+
self.ons = tuple(ons)
|
|
64
|
+
return self
|
|
65
|
+
|
|
66
|
+
def set(self, **kwargs):
|
|
67
|
+
self.onkwargs.update(self.mapping.values(kwargs))
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
def compile(self, compiler: Compiler) -> str:
|
|
71
|
+
if self.onkwargs and not self.ons:
|
|
72
|
+
raise ValueError('set() requires on() conflict targets')
|
|
73
|
+
if self.kwargs:
|
|
74
|
+
sql = 'INSERT INTO {}({}) VALUES({})'.format(
|
|
75
|
+
self.mapping.target,
|
|
76
|
+
', '.join(identifier(key) for key in self.kwargs),
|
|
77
|
+
', '.join(compiler.bind(value, valuetype(spec)) for spec, value in self.kwargs.values()),
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
sql = 'INSERT INTO {} DEFAULT VALUES'.format(self.mapping.target)
|
|
81
|
+
if self.ons:
|
|
82
|
+
sql += ' ON CONFLICT ({}) DO '.format(', '.join(identifier(key) for key in self.ons))
|
|
83
|
+
if self.onkwargs:
|
|
84
|
+
sql += 'UPDATE SET {}'.format(', '.join(
|
|
85
|
+
'{}={}'.format(identifier(key), compiler.bind(value, valuetype(spec)))
|
|
86
|
+
for key, (spec, value) in self.onkwargs.items()
|
|
87
|
+
))
|
|
88
|
+
else:
|
|
89
|
+
sql += 'NOTHING'
|
|
90
|
+
return sql + ' RETURNING *'
|
|
91
|
+
|
|
92
|
+
def fetch(
|
|
93
|
+
self,
|
|
94
|
+
cursor,
|
|
95
|
+
filter=None,
|
|
96
|
+
fetch=None,
|
|
97
|
+
):
|
|
98
|
+
return first(cursor, filter, fetch)
|