DefPixel-PostgreSQL 0.1.6__91-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.
Files changed (68) hide show
  1. DefPixel/DataAccessObject/Implements/PostgreSQL/Adapters/Array.py +16 -0
  2. DefPixel/DataAccessObject/Implements/PostgreSQL/Adapters/JavaScriptObjectNotation.py +74 -0
  3. DefPixel/DataAccessObject/Implements/PostgreSQL/Adapters/__init__.py +51 -0
  4. DefPixel/DataAccessObject/Implements/PostgreSQL/Column.py +34 -0
  5. DefPixel/DataAccessObject/Implements/PostgreSQL/Compiler.py +161 -0
  6. DefPixel/DataAccessObject/Implements/PostgreSQL/Configuration.py +116 -0
  7. DefPixel/DataAccessObject/Implements/PostgreSQL/Connection.py +138 -0
  8. DefPixel/DataAccessObject/Implements/PostgreSQL/Constraint.py +18 -0
  9. DefPixel/DataAccessObject/Implements/PostgreSQL/Constraints/Check.py +23 -0
  10. DefPixel/DataAccessObject/Implements/PostgreSQL/Constraints/Columns.py +24 -0
  11. DefPixel/DataAccessObject/Implements/PostgreSQL/Constraints/ForeignKey.py +56 -0
  12. DefPixel/DataAccessObject/Implements/PostgreSQL/Constraints/PrimaryKey.py +21 -0
  13. DefPixel/DataAccessObject/Implements/PostgreSQL/Constraints/Unique.py +25 -0
  14. DefPixel/DataAccessObject/Implements/PostgreSQL/Constraints/__init__.py +13 -0
  15. DefPixel/DataAccessObject/Implements/PostgreSQL/Context.py +48 -0
  16. DefPixel/DataAccessObject/Implements/PostgreSQL/Cursor.py +123 -0
  17. DefPixel/DataAccessObject/Implements/PostgreSQL/Executors/Create.py +181 -0
  18. DefPixel/DataAccessObject/Implements/PostgreSQL/Executors/Delete.py +31 -0
  19. DefPixel/DataAccessObject/Implements/PostgreSQL/Executors/Drop.py +53 -0
  20. DefPixel/DataAccessObject/Implements/PostgreSQL/Executors/Get.py +24 -0
  21. DefPixel/DataAccessObject/Implements/PostgreSQL/Executors/Insert.py +98 -0
  22. DefPixel/DataAccessObject/Implements/PostgreSQL/Executors/Query.py +37 -0
  23. DefPixel/DataAccessObject/Implements/PostgreSQL/Executors/Select.py +122 -0
  24. DefPixel/DataAccessObject/Implements/PostgreSQL/Executors/Update.py +57 -0
  25. DefPixel/DataAccessObject/Implements/PostgreSQL/Executors/__init__.py +19 -0
  26. DefPixel/DataAccessObject/Implements/PostgreSQL/Expr.py +11 -0
  27. DefPixel/DataAccessObject/Implements/PostgreSQL/Exprs/Common.py +180 -0
  28. DefPixel/DataAccessObject/Implements/PostgreSQL/Exprs/Compare.py +87 -0
  29. DefPixel/DataAccessObject/Implements/PostgreSQL/Exprs/Condition.py +155 -0
  30. DefPixel/DataAccessObject/Implements/PostgreSQL/Exprs/JavaScriptObjectNotation.py +53 -0
  31. DefPixel/DataAccessObject/Implements/PostgreSQL/Exprs/Operator.py +42 -0
  32. DefPixel/DataAccessObject/Implements/PostgreSQL/Exprs/String.py +48 -0
  33. DefPixel/DataAccessObject/Implements/PostgreSQL/Exprs/__init__.py +75 -0
  34. DefPixel/DataAccessObject/Implements/PostgreSQL/Function.py +11 -0
  35. DefPixel/DataAccessObject/Implements/PostgreSQL/Functions/Common.py +142 -0
  36. DefPixel/DataAccessObject/Implements/PostgreSQL/Functions/DateTime.py +219 -0
  37. DefPixel/DataAccessObject/Implements/PostgreSQL/Functions/JavaScriptObjectNotation.py +100 -0
  38. DefPixel/DataAccessObject/Implements/PostgreSQL/Functions/Sequence.py +55 -0
  39. DefPixel/DataAccessObject/Implements/PostgreSQL/Functions/__init__.py +91 -0
  40. DefPixel/DataAccessObject/Implements/PostgreSQL/Index.py +135 -0
  41. DefPixel/DataAccessObject/Implements/PostgreSQL/Joins/FullOuter.py +15 -0
  42. DefPixel/DataAccessObject/Implements/PostgreSQL/Joins/Inner.py +15 -0
  43. DefPixel/DataAccessObject/Implements/PostgreSQL/Joins/Join.py +41 -0
  44. DefPixel/DataAccessObject/Implements/PostgreSQL/Joins/LeftOuter.py +15 -0
  45. DefPixel/DataAccessObject/Implements/PostgreSQL/Joins/RightOuter.py +15 -0
  46. DefPixel/DataAccessObject/Implements/PostgreSQL/Joins/__init__.py +15 -0
  47. DefPixel/DataAccessObject/Implements/PostgreSQL/Mapping.py +359 -0
  48. DefPixel/DataAccessObject/Implements/PostgreSQL/Orders.py +59 -0
  49. DefPixel/DataAccessObject/Implements/PostgreSQL/Pool.py +84 -0
  50. DefPixel/DataAccessObject/Implements/PostgreSQL/Schema.py +28 -0
  51. DefPixel/DataAccessObject/Implements/PostgreSQL/Sequence.py +55 -0
  52. DefPixel/DataAccessObject/Implements/PostgreSQL/Session.py +84 -0
  53. DefPixel/DataAccessObject/Implements/PostgreSQL/Type.py +113 -0
  54. DefPixel/DataAccessObject/Implements/PostgreSQL/Types/Array.py +61 -0
  55. DefPixel/DataAccessObject/Implements/PostgreSQL/Types/Binary.py +34 -0
  56. DefPixel/DataAccessObject/Implements/PostgreSQL/Types/Bool.py +29 -0
  57. DefPixel/DataAccessObject/Implements/PostgreSQL/Types/DateTime.py +120 -0
  58. DefPixel/DataAccessObject/Implements/PostgreSQL/Types/JavaScriptObjectNotation.py +52 -0
  59. DefPixel/DataAccessObject/Implements/PostgreSQL/Types/Numeric.py +151 -0
  60. DefPixel/DataAccessObject/Implements/PostgreSQL/Types/Size.py +13 -0
  61. DefPixel/DataAccessObject/Implements/PostgreSQL/Types/String.py +80 -0
  62. DefPixel/DataAccessObject/Implements/PostgreSQL/Types/__init__.py +76 -0
  63. DefPixel/DataAccessObject/Implements/PostgreSQL/__init__.py +85 -0
  64. defpixel_postgresql-0.1.6.dist-info/METADATA +184 -0
  65. defpixel_postgresql-0.1.6.dist-info/RECORD +68 -0
  66. defpixel_postgresql-0.1.6.dist-info/WHEEL +6 -0
  67. defpixel_postgresql-0.1.6.dist-info/licenses/LICENSE +674 -0
  68. defpixel_postgresql-0.1.6.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,13 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from .PrimaryKey import PrimaryKey
4
+ from .ForeignKey import ForeignKey
5
+ from .Unique import Unique
6
+ from .Check import Check
7
+
8
+ __all__ = (
9
+ 'PrimaryKey',
10
+ 'ForeignKey',
11
+ 'Unique',
12
+ 'Check',
13
+ )
@@ -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)