Skip to content
This repository was archived by the owner on May 17, 2024. It is now read-only.

Commit 973a63d

Browse files
support PG json vs RS super matching
1 parent ffc4afe commit 973a63d

File tree

4 files changed

+95
-1
lines changed

4 files changed

+95
-1
lines changed

sqeleton/abcs/database_types.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,18 @@ class Text(StringType):
134134
supported = False
135135

136136

137+
class JSONType(ColType):
138+
pass
139+
140+
141+
class RedShiftSuper(JSONType):
142+
pass
143+
144+
145+
class PostgresqlJSON(JSONType):
146+
pass
147+
148+
137149
@dataclass
138150
class Integer(NumericType, IKey):
139151
precision: int = 0

sqeleton/databases/base.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
DbTime,
3535
DbPath,
3636
Boolean,
37+
JSONType
3738
)
3839
from ..abcs.mixins import Compilable
3940
from ..abcs.mixins import AbstractMixin_Schema, AbstractMixin_RandomSample, AbstractMixin_NormalizeValue
@@ -246,6 +247,9 @@ def parse_type(
246247
elif issubclass(cls, (Text, Native_UUID)):
247248
return cls()
248249

250+
elif issubclass(cls, JSONType):
251+
return cls()
252+
249253
raise TypeError(f"Parsing {type_repr} returned an unknown type '{cls}'.")
250254

251255
def _convert_db_precision_to_digits(self, p: int) -> int:

sqeleton/databases/postgresql.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
from ..abcs.database_types import (
2+
ColType,
23
Timestamp,
34
TimestampTZ,
45
Float,
56
Decimal,
67
Integer,
78
TemporalType,
9+
ColType_UUID,
810
Native_UUID,
911
Text,
1012
FractionalType,
1113
Boolean,
1214
Date,
15+
PostgresqlJSON
1316
)
1417
from ..abcs.mixins import AbstractMixin_MD5, AbstractMixin_NormalizeValue
1518
from .base import BaseDialect, ThreadedDatabase, import_helper, ConnectError, Mixin_Schema
@@ -48,6 +51,37 @@ def normalize_number(self, value: str, coltype: FractionalType) -> str:
4851
def normalize_boolean(self, value: str, _coltype: Boolean) -> str:
4952
return self.to_string(f"{value}::int")
5053

54+
def normalize_json(self, value: str, _coltype: PostgresqlJSON) -> str:
55+
return f"replace({value}::text, '\": \"', '\":\"')" # minified json
56+
57+
def normalize_value_by_type(self, value: str, coltype: ColType) -> str:
58+
"""Creates an SQL expression, that converts 'value' to a normalized representation.
59+
60+
The returned expression must accept any SQL value, and return a string.
61+
62+
The default implementation dispatches to a method according to `coltype`:
63+
64+
::
65+
66+
TemporalType -> normalize_timestamp()
67+
FractionalType -> normalize_number()
68+
*else* -> to_string()
69+
70+
(`Integer` falls in the *else* category)
71+
72+
"""
73+
if isinstance(coltype, TemporalType):
74+
return self.normalize_timestamp(value, coltype)
75+
elif isinstance(coltype, FractionalType):
76+
return self.normalize_number(value, coltype)
77+
elif isinstance(coltype, ColType_UUID):
78+
return self.normalize_uuid(value, coltype)
79+
elif isinstance(coltype, Boolean):
80+
return self.normalize_boolean(value, coltype)
81+
elif isinstance(coltype, PostgresqlJSON):
82+
return self.normalize_json(value, coltype)
83+
return self.to_string(value)
84+
5185

5286
class PostgresqlDialect(BaseDialect, Mixin_Schema):
5387
name = "PostgreSQL"
@@ -74,6 +108,8 @@ class PostgresqlDialect(BaseDialect, Mixin_Schema):
74108
"character varying": Text,
75109
"varchar": Text,
76110
"text": Text,
111+
# JSON
112+
"json": PostgresqlJSON,
77113
# UUID
78114
"uuid": Native_UUID,
79115
# Boolean

sqeleton/databases/redshift.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
from typing import List, Dict
2-
from ..abcs.database_types import Float, TemporalType, FractionalType, DbPath
2+
from ..abcs.database_types import (
3+
Float,
4+
TemporalType,
5+
FractionalType,
6+
DbPath,
7+
RedShiftSuper,
8+
ColType,
9+
ColType_UUID,
10+
Boolean
11+
)
312
from ..abcs.mixins import AbstractMixin_MD5
413
from .postgresql import (
514
PostgreSQL,
@@ -40,13 +49,46 @@ def normalize_timestamp(self, value: str, coltype: TemporalType) -> str:
4049
def normalize_number(self, value: str, coltype: FractionalType) -> str:
4150
return self.to_string(f"{value}::decimal(38,{coltype.precision})")
4251

52+
def normalize_super(self, value: str, _coltype: RedShiftSuper) -> str:
53+
return f'nvl2({value}, json_serialize({value}), NULL)' # only ::varchar causes redshift to return null
54+
55+
def normalize_value_by_type(self, value: str, coltype: ColType) -> str:
56+
"""Creates an SQL expression, that converts 'value' to a normalized representation.
57+
58+
The returned expression must accept any SQL value, and return a string.
59+
60+
The default implementation dispatches to a method according to `coltype`:
61+
62+
::
63+
64+
TemporalType -> normalize_timestamp()
65+
FractionalType -> normalize_number()
66+
*else* -> to_string()
67+
68+
(`Integer` falls in the *else* category)
69+
70+
"""
71+
if isinstance(coltype, TemporalType):
72+
return self.normalize_timestamp(value, coltype)
73+
elif isinstance(coltype, FractionalType):
74+
return self.normalize_number(value, coltype)
75+
elif isinstance(coltype, ColType_UUID):
76+
return self.normalize_uuid(value, coltype)
77+
elif isinstance(coltype, Boolean):
78+
return self.normalize_boolean(value, coltype)
79+
elif isinstance(coltype, RedShiftSuper):
80+
return self.normalize_super(value, coltype)
81+
return self.to_string(value)
82+
4383

4484
class Dialect(PostgresqlDialect):
4585
name = "Redshift"
4686
TYPE_CLASSES = {
4787
**PostgresqlDialect.TYPE_CLASSES,
4888
"double": Float,
4989
"real": Float,
90+
# JSON
91+
"super": RedShiftSuper
5092
}
5193
SUPPORTS_INDEXES = False
5294

0 commit comments

Comments
 (0)